radiant 0.6.6 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of radiant might be problematic. Click here for more details.

Files changed (208) hide show
  1. data/CHANGELOG +15 -0
  2. data/CONTRIBUTORS +8 -0
  3. data/app/controllers/admin/page_controller.rb +1 -11
  4. data/app/controllers/admin/welcome_controller.rb +5 -6
  5. data/app/controllers/application.rb +2 -0
  6. data/app/controllers/site_controller.rb +1 -0
  7. data/app/helpers/admin/page_helper.rb +36 -0
  8. data/app/helpers/admin/regions_helper.rb +28 -0
  9. data/app/helpers/admin/user_helper.rb +6 -0
  10. data/app/helpers/application_helper.rb +2 -1
  11. data/app/models/user.rb +7 -8
  12. data/app/views/admin/extension/index.html.haml +28 -0
  13. data/app/views/admin/layout/edit.html.haml +44 -0
  14. data/app/views/admin/layout/index.html.haml +25 -0
  15. data/app/views/admin/layout/remove.html.haml +16 -0
  16. data/app/views/admin/page/_meta_row.html.haml +6 -0
  17. data/app/views/admin/page/_node.html.haml +25 -0
  18. data/app/views/admin/page/_part.html.haml +17 -0
  19. data/app/views/admin/page/_tag_reference.html.haml +3 -0
  20. data/app/views/admin/page/children.html.haml +2 -0
  21. data/app/views/admin/page/edit.html.haml +114 -0
  22. data/app/views/admin/page/index.html.haml +28 -0
  23. data/app/views/admin/page/remove.html.haml +20 -0
  24. data/app/views/admin/snippet/edit.html.haml +36 -0
  25. data/app/views/admin/snippet/index.html.haml +20 -0
  26. data/app/views/admin/snippet/remove.html.haml +19 -0
  27. data/app/views/admin/user/edit.html.haml +80 -0
  28. data/app/views/admin/user/index.html.haml +23 -0
  29. data/app/views/admin/user/preferences.html.haml +28 -0
  30. data/app/views/admin/user/remove.html.haml +18 -0
  31. data/app/views/admin/welcome/login.html.haml +44 -0
  32. data/app/views/layouts/application.html.haml +54 -0
  33. data/app/views/site/not_found.html.haml +3 -0
  34. data/db/migrate/019_add_salt_to_users.rb +11 -0
  35. data/db/schema.rb +2 -1
  36. data/lib/login_system.rb +1 -0
  37. data/lib/radiant.rb +1 -1
  38. data/lib/radiant/admin_ui.rb +65 -0
  39. data/lib/radiant/admin_ui/region_partials.rb +18 -0
  40. data/lib/radiant/admin_ui/region_set.rb +35 -0
  41. data/lib/tasks/extensions.rake +33 -0
  42. data/public/javascripts/admin/admin.js +8 -2
  43. data/public/javascripts/admin/sitemap.js +2 -1
  44. data/public/stylesheets/admin/main.css +4 -0
  45. data/spec/controllers/admin/abstract_model_controller_spec.rb +2 -0
  46. data/spec/controllers/admin/page_controller_spec.rb +3 -28
  47. data/spec/controllers/admin/user_controller_spec.rb +1 -1
  48. data/spec/controllers/admin/welcome_controller_spec.rb +26 -0
  49. data/spec/helpers/admin/page_helper_spec.rb +4 -0
  50. data/spec/helpers/admin/regions_helper_spec.rb +47 -0
  51. data/spec/helpers/admin/user_helper_spec.rb +7 -0
  52. data/spec/helpers/application_helper_spec.rb +7 -3
  53. data/spec/lib/login_system_spec.rb +5 -0
  54. data/spec/lib/radiant/admin_ui/region_partials_spec.rb +35 -0
  55. data/spec/lib/radiant/admin_ui/region_set_spec.rb +61 -0
  56. data/spec/lib/radiant/admin_ui_spec.rb +74 -18
  57. data/spec/models/user_spec.rb +11 -4
  58. data/spec/scenarios/users_scenario.rb +2 -2
  59. data/vendor/plugins/haml/MIT-LICENSE +20 -0
  60. data/vendor/plugins/haml/README.rdoc +319 -0
  61. data/vendor/plugins/haml/Rakefile +158 -0
  62. data/vendor/plugins/haml/TODO +9 -0
  63. data/vendor/plugins/haml/VERSION +1 -0
  64. data/vendor/plugins/haml/bin/css2sass +7 -0
  65. data/vendor/plugins/haml/bin/haml +8 -0
  66. data/vendor/plugins/haml/bin/html2haml +7 -0
  67. data/vendor/plugins/haml/bin/sass +8 -0
  68. data/vendor/plugins/haml/extra/haml-mode.el +328 -0
  69. data/vendor/plugins/haml/extra/sass-mode.el +88 -0
  70. data/vendor/plugins/haml/init.rb +2 -0
  71. data/vendor/plugins/haml/lib/haml.rb +977 -0
  72. data/vendor/plugins/haml/lib/haml/buffer.rb +229 -0
  73. data/vendor/plugins/haml/lib/haml/engine.rb +274 -0
  74. data/vendor/plugins/haml/lib/haml/error.rb +23 -0
  75. data/vendor/plugins/haml/lib/haml/exec.rb +347 -0
  76. data/vendor/plugins/haml/lib/haml/filters.rb +249 -0
  77. data/vendor/plugins/haml/lib/haml/helpers.rb +413 -0
  78. data/vendor/plugins/haml/lib/haml/helpers/action_view_extensions.rb +45 -0
  79. data/vendor/plugins/haml/lib/haml/helpers/action_view_mods.rb +122 -0
  80. data/vendor/plugins/haml/lib/haml/html.rb +188 -0
  81. data/vendor/plugins/haml/lib/haml/precompiler.rb +757 -0
  82. data/vendor/plugins/haml/lib/haml/template.rb +43 -0
  83. data/vendor/plugins/haml/lib/haml/template/patch.rb +58 -0
  84. data/vendor/plugins/haml/lib/haml/template/plugin.rb +72 -0
  85. data/vendor/plugins/haml/lib/sass.rb +833 -0
  86. data/vendor/plugins/haml/lib/sass/constant.rb +245 -0
  87. data/vendor/plugins/haml/lib/sass/constant/color.rb +101 -0
  88. data/vendor/plugins/haml/lib/sass/constant/literal.rb +53 -0
  89. data/vendor/plugins/haml/lib/sass/constant/number.rb +87 -0
  90. data/vendor/plugins/haml/lib/sass/constant/operation.rb +30 -0
  91. data/vendor/plugins/haml/lib/sass/constant/string.rb +22 -0
  92. data/vendor/plugins/haml/lib/sass/css.rb +378 -0
  93. data/vendor/plugins/haml/lib/sass/engine.rb +459 -0
  94. data/vendor/plugins/haml/lib/sass/error.rb +35 -0
  95. data/vendor/plugins/haml/lib/sass/plugin.rb +165 -0
  96. data/vendor/plugins/haml/lib/sass/plugin/merb.rb +56 -0
  97. data/vendor/plugins/haml/lib/sass/plugin/rails.rb +24 -0
  98. data/vendor/plugins/haml/lib/sass/tree/attr_node.rb +53 -0
  99. data/vendor/plugins/haml/lib/sass/tree/comment_node.rb +20 -0
  100. data/vendor/plugins/haml/lib/sass/tree/directive_node.rb +46 -0
  101. data/vendor/plugins/haml/lib/sass/tree/node.rb +42 -0
  102. data/vendor/plugins/haml/lib/sass/tree/rule_node.rb +89 -0
  103. data/vendor/plugins/haml/lib/sass/tree/value_node.rb +16 -0
  104. data/vendor/plugins/haml/test/benchmark.rb +82 -0
  105. data/vendor/plugins/haml/test/haml/engine_test.rb +587 -0
  106. data/vendor/plugins/haml/test/haml/helper_test.rb +187 -0
  107. data/vendor/plugins/haml/test/haml/html2haml_test.rb +60 -0
  108. data/vendor/plugins/haml/test/haml/markaby/standard.mab +52 -0
  109. data/vendor/plugins/haml/test/haml/mocks/article.rb +6 -0
  110. data/vendor/plugins/haml/test/haml/results/content_for_layout.xhtml +16 -0
  111. data/vendor/plugins/haml/test/haml/results/eval_suppressed.xhtml +11 -0
  112. data/vendor/plugins/haml/test/haml/results/filters.xhtml +82 -0
  113. data/vendor/plugins/haml/test/haml/results/helpers.xhtml +94 -0
  114. data/vendor/plugins/haml/test/haml/results/helpful.xhtml +10 -0
  115. data/vendor/plugins/haml/test/haml/results/just_stuff.xhtml +64 -0
  116. data/vendor/plugins/haml/test/haml/results/list.xhtml +12 -0
  117. data/vendor/plugins/haml/test/haml/results/original_engine.xhtml +22 -0
  118. data/vendor/plugins/haml/test/haml/results/partials.xhtml +21 -0
  119. data/vendor/plugins/haml/test/haml/results/silent_script.xhtml +74 -0
  120. data/vendor/plugins/haml/test/haml/results/standard.xhtml +42 -0
  121. data/vendor/plugins/haml/test/haml/results/tag_parsing.xhtml +28 -0
  122. data/vendor/plugins/haml/test/haml/results/very_basic.xhtml +7 -0
  123. data/vendor/plugins/haml/test/haml/results/whitespace_handling.xhtml +94 -0
  124. data/vendor/plugins/haml/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  125. data/vendor/plugins/haml/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  126. data/vendor/plugins/haml/test/haml/rhtml/action_view.rhtml +62 -0
  127. data/vendor/plugins/haml/test/haml/rhtml/standard.rhtml +54 -0
  128. data/vendor/plugins/haml/test/haml/template_test.rb +168 -0
  129. data/vendor/plugins/haml/test/haml/templates/_av_partial_1.haml +9 -0
  130. data/vendor/plugins/haml/test/haml/templates/_av_partial_2.haml +5 -0
  131. data/vendor/plugins/haml/test/haml/templates/_partial.haml +8 -0
  132. data/vendor/plugins/haml/test/haml/templates/_text_area.haml +3 -0
  133. data/vendor/plugins/haml/test/haml/templates/action_view.haml +47 -0
  134. data/vendor/plugins/haml/test/haml/templates/breakage.haml +8 -0
  135. data/vendor/plugins/haml/test/haml/templates/content_for_layout.haml +10 -0
  136. data/vendor/plugins/haml/test/haml/templates/eval_suppressed.haml +11 -0
  137. data/vendor/plugins/haml/test/haml/templates/filters.haml +81 -0
  138. data/vendor/plugins/haml/test/haml/templates/helpers.haml +69 -0
  139. data/vendor/plugins/haml/test/haml/templates/helpful.haml +11 -0
  140. data/vendor/plugins/haml/test/haml/templates/just_stuff.haml +77 -0
  141. data/vendor/plugins/haml/test/haml/templates/list.haml +12 -0
  142. data/vendor/plugins/haml/test/haml/templates/original_engine.haml +17 -0
  143. data/vendor/plugins/haml/test/haml/templates/partialize.haml +1 -0
  144. data/vendor/plugins/haml/test/haml/templates/partials.haml +12 -0
  145. data/vendor/plugins/haml/test/haml/templates/silent_script.haml +40 -0
  146. data/vendor/plugins/haml/test/haml/templates/standard.haml +42 -0
  147. data/vendor/plugins/haml/test/haml/templates/tag_parsing.haml +24 -0
  148. data/vendor/plugins/haml/test/haml/templates/very_basic.haml +4 -0
  149. data/vendor/plugins/haml/test/haml/templates/whitespace_handling.haml +87 -0
  150. data/vendor/plugins/haml/test/haml/test_helper.rb +15 -0
  151. data/vendor/plugins/haml/test/profile.rb +65 -0
  152. data/vendor/plugins/haml/test/sass/engine_test.rb +276 -0
  153. data/vendor/plugins/haml/test/sass/plugin_test.rb +159 -0
  154. data/vendor/plugins/haml/test/sass/results/alt.css +4 -0
  155. data/vendor/plugins/haml/test/sass/results/basic.css +9 -0
  156. data/vendor/plugins/haml/test/sass/results/compact.css +5 -0
  157. data/vendor/plugins/haml/test/sass/results/complex.css +87 -0
  158. data/vendor/plugins/haml/test/sass/results/compressed.css +1 -0
  159. data/vendor/plugins/haml/test/sass/results/constants.css +14 -0
  160. data/vendor/plugins/haml/test/sass/results/expanded.css +19 -0
  161. data/vendor/plugins/haml/test/sass/results/import.css +29 -0
  162. data/vendor/plugins/haml/test/sass/results/mixins.css +95 -0
  163. data/vendor/plugins/haml/test/sass/results/multiline.css +24 -0
  164. data/vendor/plugins/haml/test/sass/results/nested.css +22 -0
  165. data/vendor/plugins/haml/test/sass/results/parent_ref.css +13 -0
  166. data/vendor/plugins/haml/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/vendor/plugins/haml/test/sass/results/subdir/subdir.css +1 -0
  168. data/vendor/plugins/haml/test/sass/templates/_partial.sass +2 -0
  169. data/vendor/plugins/haml/test/sass/templates/alt.sass +16 -0
  170. data/vendor/plugins/haml/test/sass/templates/basic.sass +23 -0
  171. data/vendor/plugins/haml/test/sass/templates/bork.sass +2 -0
  172. data/vendor/plugins/haml/test/sass/templates/bork2.sass +2 -0
  173. data/vendor/plugins/haml/test/sass/templates/compact.sass +17 -0
  174. data/vendor/plugins/haml/test/sass/templates/complex.sass +310 -0
  175. data/vendor/plugins/haml/test/sass/templates/compressed.sass +15 -0
  176. data/vendor/plugins/haml/test/sass/templates/constants.sass +97 -0
  177. data/vendor/plugins/haml/test/sass/templates/expanded.sass +17 -0
  178. data/vendor/plugins/haml/test/sass/templates/import.sass +11 -0
  179. data/vendor/plugins/haml/test/sass/templates/importee.sass +14 -0
  180. data/vendor/plugins/haml/test/sass/templates/mixins.sass +76 -0
  181. data/vendor/plugins/haml/test/sass/templates/multiline.sass +20 -0
  182. data/vendor/plugins/haml/test/sass/templates/nested.sass +25 -0
  183. data/vendor/plugins/haml/test/sass/templates/parent_ref.sass +25 -0
  184. data/vendor/plugins/haml/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  185. data/vendor/plugins/haml/test/sass/templates/subdir/subdir.sass +6 -0
  186. metadata +185 -24
  187. data/app/views/admin/extension/index.html.erb +0 -40
  188. data/app/views/admin/layout/edit.html.erb +0 -39
  189. data/app/views/admin/layout/index.html.erb +0 -38
  190. data/app/views/admin/layout/remove.html.erb +0 -17
  191. data/app/views/admin/page/_meta_row.html.erb +0 -4
  192. data/app/views/admin/page/_node.html.erb +0 -28
  193. data/app/views/admin/page/_part.html.erb +0 -13
  194. data/app/views/admin/page/_tag_reference.html.erb +0 -4
  195. data/app/views/admin/page/children.html.erb +0 -4
  196. data/app/views/admin/page/edit.html.erb +0 -140
  197. data/app/views/admin/page/index.html.erb +0 -31
  198. data/app/views/admin/page/remove.html.erb +0 -14
  199. data/app/views/admin/snippet/edit.html.erb +0 -29
  200. data/app/views/admin/snippet/index.html.erb +0 -36
  201. data/app/views/admin/snippet/remove.html.erb +0 -16
  202. data/app/views/admin/user/edit.html.erb +0 -54
  203. data/app/views/admin/user/index.html.erb +0 -43
  204. data/app/views/admin/user/preferences.html.erb +0 -29
  205. data/app/views/admin/user/remove.html.erb +0 -16
  206. data/app/views/admin/welcome/login.html.erb +0 -51
  207. data/app/views/layouts/application.html.erb +0 -83
  208. data/app/views/site/not_found.html.erb +0 -3
@@ -0,0 +1,122 @@
1
+ if defined?(ActionView) and not defined?(Merb::Plugins)
2
+ module ActionView
3
+ class Base # :nodoc:
4
+ def render_with_haml(*args, &block)
5
+ return non_haml { render_without_haml(*args, &block) } if is_haml?
6
+ render_without_haml(*args, &block)
7
+ end
8
+ alias_method :render_without_haml, :render
9
+ alias_method :render, :render_with_haml
10
+ end
11
+
12
+ # This overrides various helpers in ActionView
13
+ # to make them work more effectively with Haml.
14
+ module Helpers
15
+ # :stopdoc:
16
+ module CaptureHelper
17
+ def capture_with_haml(*args, &block)
18
+ if is_haml?
19
+ capture_haml(*args, &block)
20
+ else
21
+ capture_without_haml(*args, &block)
22
+ end
23
+ end
24
+ alias_method :capture_without_haml, :capture
25
+ alias_method :capture, :capture_with_haml
26
+
27
+ def capture_erb_with_buffer_with_haml(*args, &block)
28
+ if is_haml?
29
+ capture_haml_with_buffer(*args, &block)
30
+ else
31
+ capture_erb_with_buffer_without_haml(*args, &block)
32
+ end
33
+ end
34
+ alias_method :capture_erb_with_buffer_without_haml, :capture_erb_with_buffer
35
+ alias_method :capture_erb_with_buffer, :capture_erb_with_buffer_with_haml
36
+ end
37
+
38
+ module TextHelper
39
+ def concat_with_haml(string, binding = nil)
40
+ if is_haml?
41
+ haml_buffer.buffer.concat(string)
42
+ else
43
+ concat_without_haml(string, binding)
44
+ end
45
+ end
46
+ alias_method :concat_without_haml, :concat
47
+ alias_method :concat, :concat_with_haml
48
+ end
49
+
50
+ module TagHelper
51
+ def content_tag_with_haml(name, *args, &block)
52
+ content = content_tag_without_haml(name, *args, &block)
53
+
54
+ if is_haml? && haml_buffer.options[:preserve].include?(name.to_s)
55
+ content = Haml::Helpers.preserve content
56
+ end
57
+
58
+ content
59
+ end
60
+ alias_method :content_tag_without_haml, :content_tag
61
+ alias_method :content_tag, :content_tag_with_haml
62
+ end
63
+
64
+ class InstanceTag
65
+ # Includes TagHelper
66
+
67
+ def haml_buffer
68
+ @template_object.send :haml_buffer
69
+ end
70
+
71
+ def is_haml?
72
+ @template_object.send :is_haml?
73
+ end
74
+
75
+ alias_method :content_tag_without_haml, :content_tag
76
+ alias_method :content_tag, :content_tag_with_haml
77
+ end
78
+
79
+ module FormTagHelper
80
+ def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
81
+ if is_haml?
82
+ if block_given?
83
+ oldproc = proc
84
+ proc = haml_bind_proc do |*args|
85
+ concat "\n"
86
+ tab_up
87
+ oldproc.call(*args)
88
+ tab_down
89
+ end
90
+ end
91
+ res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n"
92
+ concat "\n" if block_given? && is_haml?
93
+ res
94
+ else
95
+ form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc)
96
+ end
97
+ end
98
+ alias_method :form_tag_without_haml, :form_tag
99
+ alias_method :form_tag, :form_tag_with_haml
100
+ end
101
+
102
+ module FormHelper
103
+ def form_for_with_haml(object_name, *args, &proc)
104
+ if block_given? && is_haml?
105
+ oldproc = proc
106
+ proc = haml_bind_proc do |*args|
107
+ tab_up
108
+ oldproc.call(*args)
109
+ tab_down
110
+ end
111
+ end
112
+ form_for_without_haml(object_name, *args, &proc)
113
+ concat "\n" if block_given? && is_haml?
114
+ end
115
+ alias_method :form_for_without_haml, :form_for
116
+ alias_method :form_for, :form_for_with_haml
117
+ end
118
+ # :startdoc:
119
+ end
120
+ end
121
+ end
122
+
@@ -0,0 +1,188 @@
1
+ require File.dirname(__FILE__) + '/../haml'
2
+
3
+ require 'haml/engine'
4
+ require 'rubygems'
5
+ require 'hpricot'
6
+ require 'cgi'
7
+
8
+ module Haml
9
+ # This class contains the functionality used in the +html2haml+ utility,
10
+ # namely converting HTML documents to Haml templates.
11
+ # It depends on Hpricot for HTML parsing (http://code.whytheluckystiff.net/hpricot/).
12
+ class HTML
13
+ # Creates a new instance of Haml::HTML that will compile the given template,
14
+ # which can either be a string containing HTML or an Hpricot node,
15
+ # to a Haml string when +render+ is called.
16
+ def initialize(template, options = {})
17
+ @@options = options
18
+
19
+ if template.is_a? Hpricot::Node
20
+ @template = template
21
+ else
22
+ if template.is_a? IO
23
+ template = template.read
24
+ end
25
+
26
+ if @@options[:rhtml]
27
+ match_to_html(template, /<%=(.*?)-?%>/m, 'loud')
28
+ match_to_html(template, /<%(.*?)-?%>/m, 'silent')
29
+ end
30
+
31
+ method = @@options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot)
32
+ @template = method.call(template)
33
+ end
34
+ end
35
+
36
+ # Processes the document and returns the result as a string
37
+ # containing the Haml template.
38
+ def render
39
+ @template.to_haml(0)
40
+ end
41
+ alias_method :to_haml, :render
42
+
43
+ module ::Hpricot::Node
44
+ # Returns the Haml representation of the given node,
45
+ # at the given tabulation.
46
+ def to_haml(tabs = 0)
47
+ parse_text(self.to_s, tabs)
48
+ end
49
+
50
+ private
51
+
52
+ def tabulate(tabs)
53
+ ' ' * tabs
54
+ end
55
+
56
+ def parse_text(text, tabs)
57
+ text.strip!
58
+ if text.empty?
59
+ String.new
60
+ else
61
+ lines = text.split("\n")
62
+
63
+ lines.map do |line|
64
+ line.strip!
65
+ "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
66
+ end.join
67
+ end
68
+ end
69
+ end
70
+
71
+ # :stopdoc:
72
+
73
+ def self.options
74
+ @@options
75
+ end
76
+
77
+ TEXT_REGEXP = /^(\s*).*$/
78
+
79
+ class ::Hpricot::Doc
80
+ def to_haml(tabs = 0)
81
+ output = ''
82
+ children.each { |child| output += child.to_haml(0) }
83
+ output
84
+ end
85
+ end
86
+
87
+ class ::Hpricot::XMLDecl
88
+ def to_haml(tabs = 0)
89
+ "#{tabulate(tabs)}!!! XML\n"
90
+ end
91
+ end
92
+
93
+ class ::Hpricot::DocType
94
+ def to_haml(tabs = 0)
95
+ attrs = public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0]
96
+ if attrs == nil
97
+ raise Exception.new("Invalid doctype")
98
+ end
99
+
100
+ type, version, strictness = attrs.map { |a| a.downcase }
101
+ if type == "html"
102
+ version = "1.0"
103
+ strictness = "transitional"
104
+ end
105
+
106
+ if version == "1.0" || version.empty?
107
+ version = nil
108
+ end
109
+
110
+ if strictness == 'transitional' || strictness.empty?
111
+ strictness = nil
112
+ end
113
+
114
+ version = " #{version}" if version
115
+ if strictness
116
+ strictness[0] = strictness[0] - 32
117
+ strictness = " #{strictness}"
118
+ end
119
+
120
+ "#{tabulate(tabs)}!!!#{version}#{strictness}\n"
121
+ end
122
+ end
123
+
124
+ class ::Hpricot::Comment
125
+ def to_haml(tabs = 0)
126
+ "#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}"
127
+ end
128
+ end
129
+
130
+ class ::Hpricot::Elem
131
+ def to_haml(tabs = 0)
132
+ output = "#{tabulate(tabs)}"
133
+ if HTML.options[:rhtml] && name[0...5] == 'haml:'
134
+ return output + HTML.send("haml_tag_#{name[5..-1]}",
135
+ CGI.unescapeHTML(self.innerHTML))
136
+ end
137
+
138
+ output += "%#{name}" unless name == 'div' && (attributes.include?('id') || attributes.include?('class'))
139
+
140
+ if attributes
141
+ output += "##{attributes['id']}" if attributes['id']
142
+ attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class']
143
+ remove_attribute('id')
144
+ remove_attribute('class')
145
+ output += haml_attributes if attributes.length > 0
146
+ end
147
+
148
+ output += "/" if children.length == 0
149
+ output += "\n"
150
+
151
+ self.children.each do |child|
152
+ output += child.to_haml(tabs + 1)
153
+ end
154
+
155
+ output
156
+ end
157
+
158
+ private
159
+
160
+ # Returns a string representation of an attributes hash
161
+ # that's prettier than that produced by Hash#inspect
162
+ def haml_attributes
163
+ attrs = attributes.map do |name, value|
164
+ name = name.index(/\W/) ? name.inspect : ":#{name}"
165
+ "#{name} => #{value.inspect}"
166
+ end
167
+ "{ #{attrs.join(', ')} }"
168
+ end
169
+ end
170
+
171
+ def self.haml_tag_loud(text)
172
+ "= #{text.gsub(/\n\s*/, '; ').strip}\n"
173
+ end
174
+
175
+ def self.haml_tag_silent(text)
176
+ text.split("\n").map { |line| "- #{line.strip}\n" }.join
177
+ end
178
+
179
+ private
180
+
181
+ def match_to_html(string, regex, tag)
182
+ string.gsub!(regex) do
183
+ "<haml:#{tag}>#{CGI.escapeHTML($1)}</haml:#{tag}>"
184
+ end
185
+ end
186
+ # :startdoc:
187
+ end
188
+ end
@@ -0,0 +1,757 @@
1
+ require 'strscan'
2
+
3
+ module Haml
4
+ module Precompiler
5
+ # Designates an XHTML/XML element.
6
+ ELEMENT = ?%
7
+
8
+ # Designates a <tt><div></tt> element with the given class.
9
+ DIV_CLASS = ?.
10
+
11
+ # Designates a <tt><div></tt> element with the given id.
12
+ DIV_ID = ?#
13
+
14
+ # Designates an XHTML/XML comment.
15
+ COMMENT = ?/
16
+
17
+ # Designates an XHTML doctype or script that is never HTML-escaped.
18
+ DOCTYPE = ?!
19
+
20
+ # Designates script, the result of which is output.
21
+ SCRIPT = ?=
22
+
23
+ # Designates script that is always HTML-escaped.
24
+ SANITIZE = ?&
25
+
26
+ # Designates script, the result of which is flattened and output.
27
+ FLAT_SCRIPT = ?~
28
+
29
+ # Designates script which is run but not output.
30
+ SILENT_SCRIPT = ?-
31
+
32
+ # When following SILENT_SCRIPT, designates a comment that is not output.
33
+ SILENT_COMMENT = ?#
34
+
35
+ # Designates a non-parsed line.
36
+ ESCAPE = ?\\
37
+
38
+ # Designates a block of filtered text.
39
+ FILTER = ?:
40
+
41
+ # Designates a non-parsed line. Not actually a character.
42
+ PLAIN_TEXT = -1
43
+
44
+ # Keeps track of the ASCII values of the characters that begin a
45
+ # specially-interpreted line.
46
+ SPECIAL_CHARACTERS = [
47
+ ELEMENT,
48
+ DIV_CLASS,
49
+ DIV_ID,
50
+ COMMENT,
51
+ DOCTYPE,
52
+ SCRIPT,
53
+ SANITIZE,
54
+ FLAT_SCRIPT,
55
+ SILENT_SCRIPT,
56
+ ESCAPE,
57
+ FILTER
58
+ ]
59
+
60
+ # The value of the character that designates that a line is part
61
+ # of a multiline string.
62
+ MULTILINE_CHAR_VALUE = ?|
63
+
64
+ # Characters that designate that a multiline string may be about
65
+ # to begin.
66
+ MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/]
67
+
68
+ # Keywords that appear in the middle of a Ruby block with lowered
69
+ # indentation. If a block has been started using indentation,
70
+ # lowering the indentation with one of these won't end the block.
71
+ # For example:
72
+ #
73
+ # - if foo
74
+ # %p yes!
75
+ # - else
76
+ # %p no!
77
+ #
78
+ # The block is ended after <tt>%p no!</tt>, because <tt>else</tt>
79
+ # is a member of this array.
80
+ MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
81
+
82
+ # The Regex that matches a Doctype command.
83
+ DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
84
+
85
+ # The Regex that matches a literal string or symbol value
86
+ LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/
87
+
88
+ private
89
+
90
+ # Returns the precompiled string with the preamble and postamble
91
+ def precompiled_with_ambles(local_names)
92
+ preamble = <<END.gsub("\n", ";")
93
+ extend Haml::Helpers
94
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
95
+ _erbout = _hamlout.buffer
96
+ END
97
+ postamble = <<END.gsub("\n", ";")
98
+ @haml_buffer = @haml_buffer.upper
99
+ _erbout
100
+ END
101
+ preamble + locals_code(local_names) + @precompiled + postamble
102
+ end
103
+
104
+ def locals_code(names)
105
+ names = names.keys if Hash == names
106
+
107
+ names.map do |name|
108
+ "#{name} = _haml_locals[#{name.to_sym.inspect}] || _haml_locals[#{name.to_s.inspect}]"
109
+ end.join(';') + ';'
110
+ end
111
+
112
+ Line = Struct.new("Line", :text, :unstripped, :index, :spaces, :tabs)
113
+
114
+ def precompile
115
+ @precompiled = ''
116
+ @merged_text = ''
117
+ @tab_change = 0
118
+
119
+ old_line = Line.new
120
+ (@template + "\n-#\n-#").split(/\n?\r|\r?\n/).each_with_index do |text, index|
121
+ line = Line.new text.strip, text.lstrip.chomp, index
122
+ line.spaces, line.tabs = count_soft_tabs(text)
123
+
124
+ if line.text.empty?
125
+ process_indent(old_line) if flat? && !old_line.text.empty?
126
+
127
+ unless flat?
128
+ newline
129
+ next
130
+ end
131
+
132
+ push_flat(old_line)
133
+ old_line.text, old_line.unstripped, old_line.spaces = '', '', 0
134
+ newline
135
+ next
136
+ end
137
+
138
+ suppress_render = handle_multiline(old_line) unless flat?
139
+
140
+ if old_line.text.nil? || suppress_render
141
+ old_line = line
142
+ resolve_newlines
143
+ newline
144
+ next
145
+ end
146
+
147
+ process_indent(old_line) unless old_line.text.empty?
148
+
149
+ if flat?
150
+ push_flat(old_line)
151
+ old_line = line
152
+ newline
153
+ next
154
+ end
155
+
156
+ if old_line.spaces != old_line.tabs * 2
157
+ raise SyntaxError.new(<<END.strip, 1 + old_line.index - @index)
158
+ #{old_line.spaces} space#{old_line.spaces == 1 ? ' was' : 's were'} used for indentation. Haml must be indented using two spaces.
159
+ END
160
+ end
161
+
162
+ unless old_line.text.empty? || @haml_comment
163
+ process_line(old_line.text, old_line.index, line.tabs > old_line.tabs && !line.text.empty?)
164
+ end
165
+ resolve_newlines
166
+
167
+ if !flat? && line.tabs - old_line.tabs > 1
168
+ raise SyntaxError.new(<<END.strip, 2 + old_line.index - @index)
169
+ #{line.spaces} spaces were used for indentation. Haml must be indented using two spaces.
170
+ END
171
+ end
172
+ old_line = line
173
+ newline
174
+ end
175
+
176
+ # Close all the open tags
177
+ close until @to_close_stack.empty?
178
+ flush_merged_text
179
+ end
180
+
181
+ # Processes and deals with lowering indentation.
182
+ def process_indent(line)
183
+ return unless line.tabs <= @template_tabs && @template_tabs > 0
184
+
185
+ to_close = @template_tabs - line.tabs
186
+ to_close.times { |i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text) }
187
+ end
188
+
189
+ # Processes a single line of Haml.
190
+ #
191
+ # This method doesn't return anything; it simply processes the line and
192
+ # adds the appropriate code to <tt>@precompiled</tt>.
193
+ def process_line(text, index, block_opened)
194
+ @block_opened = block_opened
195
+ @index = index + 1
196
+
197
+ case text[0]
198
+ when DIV_CLASS, DIV_ID; render_div(text)
199
+ when ELEMENT; render_tag(text)
200
+ when COMMENT; render_comment(text[1..-1].strip)
201
+ when SANITIZE
202
+ return push_script(unescape_interpolation(text[3..-1].strip), false, nil, false, true) if text[1..2] == "=="
203
+ return push_script(text[2..-1].strip, false, nil, false, true) if text[1] == SCRIPT
204
+ push_plain text
205
+ when SCRIPT
206
+ return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT
207
+ return push_script(text[1..-1], false, nil, false, true) if options[:escape_html]
208
+ push_script(text[1..-1], false)
209
+ when FLAT_SCRIPT; push_flat_script(text[1..-1])
210
+ when SILENT_SCRIPT
211
+ return start_haml_comment if text[1] == SILENT_COMMENT
212
+
213
+ push_silent(text[1..-1], true)
214
+ newline_now
215
+ if (@block_opened && !mid_block_keyword?(text)) || text[1..-1].split(' ', 2)[0] == "case"
216
+ push_and_tabulate([:script])
217
+ end
218
+ when FILTER; start_filtered(text[1..-1].downcase)
219
+ when DOCTYPE
220
+ return render_doctype(text) if text[0...3] == '!!!'
221
+ return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
222
+ return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
223
+ push_plain text
224
+ when ESCAPE; push_plain text[1..-1]
225
+ else push_plain text
226
+ end
227
+ end
228
+
229
+ # Returns whether or not the text is a silent script text with one
230
+ # of Ruby's mid-block keywords.
231
+ def mid_block_keyword?(text)
232
+ text.length > 2 && text[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(text[1..-1].split[0])
233
+ end
234
+
235
+ # Deals with all the logic of figuring out whether a given line is
236
+ # the beginning, continuation, or end of a multiline sequence.
237
+ #
238
+ # This returns whether or not the line should be
239
+ # rendered normally.
240
+ def handle_multiline(line)
241
+ text = line.text
242
+
243
+ # A multiline string is active, and is being continued
244
+ if is_multiline?(text) && @multiline
245
+ @multiline.text << text[0...-1]
246
+ return true
247
+ end
248
+
249
+ # A multiline string has just been activated, start adding the lines
250
+ if is_multiline?(text) && (MULTILINE_STARTERS.include? text[0])
251
+ @multiline = Line.new text[0...-1], nil, line.index, nil, line.tabs
252
+ process_indent(line)
253
+ return true
254
+ end
255
+
256
+ # A multiline string has just ended, make line into the result
257
+ if @multiline && !line.text.empty?
258
+ process_line(@multiline.text, @multiline.index, line.tabs > @multiline.tabs)
259
+ @multiline = nil
260
+ end
261
+
262
+ return false
263
+ end
264
+
265
+ # Checks whether or not +line+ is in a multiline sequence.
266
+ def is_multiline?(text)
267
+ text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
268
+ end
269
+
270
+ # Evaluates <tt>text</tt> in the context of the scope object, but
271
+ # does not output the result.
272
+ def push_silent(text, can_suppress = false)
273
+ flush_merged_text
274
+ return if can_suppress && options[:suppress_eval]
275
+ @precompiled << "#{text};"
276
+ end
277
+
278
+ # Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
279
+ # without parsing it.
280
+ def push_merged_text(text, tab_change = 0, try_one_liner = false)
281
+ @merged_text << (@options[:ugly] ? text : "#{' ' * @output_tabs}#{text}")
282
+ @tab_change += tab_change
283
+ end
284
+
285
+ # Concatenate <tt>text</tt> to <tt>@buffer</tt> without tabulation.
286
+ def concat_merged_text(text)
287
+ @merged_text << text
288
+ end
289
+
290
+ def push_text(text, tab_change = 0, try_one_liner = false)
291
+ push_merged_text("#{text}\n", tab_change, try_one_liner)
292
+ end
293
+
294
+ def flush_merged_text
295
+ return if @merged_text.empty?
296
+
297
+ @precompiled << "_hamlout.push_text(#{@merged_text.dump}"
298
+ @precompiled << ", #{@tab_change}" if @tab_change != 0
299
+ @precompiled << ");"
300
+ @merged_text = ''
301
+ @tab_change = 0
302
+ end
303
+
304
+ # Renders a block of text as plain text.
305
+ # Also checks for an illegally opened block.
306
+ def push_plain(text)
307
+ raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", 1) if @block_opened
308
+ push_text text
309
+ end
310
+
311
+ # Adds +text+ to <tt>@buffer</tt> while flattening text.
312
+ def push_flat(line)
313
+ unless @options[:ugly]
314
+ tabulation = line.spaces - @flat_spaces
315
+ tabulation = tabulation > -1 ? tabulation : 0
316
+ @filter_buffer << "#{' ' * tabulation}#{line.unstripped}\n"
317
+ else
318
+ @filter_buffer << "#{line.unstripped}\n"
319
+ end
320
+ end
321
+
322
+ # Causes <tt>text</tt> to be evaluated in the context of
323
+ # the scope object and the result to be added to <tt>@buffer</tt>.
324
+ #
325
+ # If <tt>preserve_script</tt> is true, Haml::Helpers#find_and_flatten is run on
326
+ # the result before it is added to <tt>@buffer</tt>
327
+ def push_script(text, preserve_script, close_tag = nil, preserve_tag = false, escape_html = false)
328
+ flush_merged_text
329
+ return if options[:suppress_eval]
330
+
331
+ raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
332
+
333
+ push_silent "haml_temp = #{text}"
334
+ newline_now
335
+ out = "haml_temp = _hamlout.push_script(haml_temp, #{preserve_script.inspect}, #{close_tag.inspect}, #{preserve_tag.inspect}, #{escape_html.inspect});"
336
+ if @block_opened
337
+ push_and_tabulate([:loud, out])
338
+ else
339
+ @precompiled << out
340
+ end
341
+ end
342
+
343
+ # Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
344
+ # to be run on it afterwards.
345
+ def push_flat_script(text)
346
+ flush_merged_text
347
+
348
+ raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
349
+ push_script(text, true)
350
+ end
351
+
352
+ def start_haml_comment
353
+ return unless @block_opened
354
+
355
+ @haml_comment = true
356
+ push_and_tabulate([:haml_comment])
357
+ end
358
+
359
+ # Closes the most recent item in <tt>@to_close_stack</tt>.
360
+ def close
361
+ tag, value = @to_close_stack.pop
362
+ case tag
363
+ when :script; close_block
364
+ when :comment; close_comment value
365
+ when :element; close_tag value
366
+ when :loud; close_loud value
367
+ when :filtered; close_filtered value
368
+ when :haml_comment; close_haml_comment
369
+ end
370
+ end
371
+
372
+ # Puts a line in <tt>@precompiled</tt> that will add the closing tag of
373
+ # the most recently opened tag.
374
+ def close_tag(tag)
375
+ @output_tabs -= 1
376
+ @template_tabs -= 1
377
+ push_text("</#{tag}>", -1)
378
+ end
379
+
380
+ # Closes a Ruby block.
381
+ def close_block
382
+ push_silent "end", true
383
+ @template_tabs -= 1
384
+ end
385
+
386
+ # Closes a comment.
387
+ def close_comment(has_conditional)
388
+ @output_tabs -= 1
389
+ @template_tabs -= 1
390
+ close_tag = has_conditional ? "<![endif]-->" : "-->"
391
+ push_text(close_tag, -1)
392
+ end
393
+
394
+ # Closes a loud Ruby block.
395
+ def close_loud(command)
396
+ push_silent 'end', true
397
+ @precompiled << command
398
+ @template_tabs -= 1
399
+ end
400
+
401
+ # Closes a filtered block.
402
+ def close_filtered(filter)
403
+ @flat_spaces = -1
404
+ filter.internal_compile(self, @filter_buffer)
405
+ @filter_buffer = nil
406
+ @template_tabs -= 1
407
+ end
408
+
409
+ def close_haml_comment
410
+ @haml_comment = false
411
+ @template_tabs -= 1
412
+ end
413
+
414
+ # Iterates through the classes and ids supplied through <tt>.</tt>
415
+ # and <tt>#</tt> syntax, and returns a hash with them as attributes,
416
+ # that can then be merged with another attributes hash.
417
+ def parse_class_and_id(list)
418
+ attributes = {}
419
+ list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
420
+ case type
421
+ when '.'
422
+ if attributes['class']
423
+ attributes['class'] += " "
424
+ else
425
+ attributes['class'] = ""
426
+ end
427
+ attributes['class'] += property
428
+ when '#'; attributes['id'] = property
429
+ end
430
+ end
431
+ attributes
432
+ end
433
+
434
+ def parse_literal_value(text)
435
+ return nil unless text
436
+ text.match(LITERAL_VALUE_REGEX)
437
+
438
+ # $2 holds the value matched by a symbol, but is nil for a string match
439
+ # $5 holds the value matched by a string
440
+ $2 || $5
441
+ end
442
+
443
+ def parse_static_hash(text)
444
+ return {} unless text
445
+
446
+ attributes = {}
447
+ text.split(',').each do |attrib|
448
+ key, value, more = attrib.split('=>')
449
+
450
+ # Make sure the key and value and only the key and value exist
451
+ # Otherwise, it's too complicated or dynamic and we'll defer it to the actual Ruby parser
452
+ key = parse_literal_value key
453
+ value = parse_literal_value value
454
+ return nil if more || key.nil? || value.nil?
455
+
456
+ attributes[key] = value
457
+ end
458
+ attributes
459
+ end
460
+
461
+ # This is a class method so it can be accessed from Buffer.
462
+ def self.build_attributes(is_html, attr_wrapper, attributes = {})
463
+ quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
464
+ other_quote_char = attr_wrapper == '"' ? "'" : '"'
465
+
466
+ result = attributes.collect do |attr, value|
467
+ next if value.nil?
468
+
469
+ if value == true
470
+ next " #{attr}" if is_html
471
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
472
+ elsif value == false
473
+ next
474
+ end
475
+
476
+ value = Haml::Helpers.escape_once(value.to_s)
477
+ # We want to decide whether or not to escape quotes
478
+ value.gsub!('&quot;', '"')
479
+ this_attr_wrapper = attr_wrapper
480
+ if value.include? attr_wrapper
481
+ if value.include? other_quote_char
482
+ value = value.gsub(attr_wrapper, quote_escape)
483
+ else
484
+ this_attr_wrapper = other_quote_char
485
+ end
486
+ end
487
+ " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
488
+ end
489
+ result.compact.sort.join
490
+ end
491
+
492
+ def prerender_tag(name, self_close, attributes)
493
+ attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
494
+ "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
495
+ end
496
+
497
+ # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
498
+ def parse_tag(line)
499
+ raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
500
+ tag_name, attributes, rest = match
501
+ attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{
502
+ if rest
503
+ object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[
504
+ attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil?
505
+ action, value = rest.scan(/([=\/\~&!]?)?(.*)?/)[0]
506
+ end
507
+ value = value.to_s.strip
508
+ [tag_name, attributes, attributes_hash, object_ref, action, value]
509
+ end
510
+
511
+ def parse_attributes(line)
512
+ scanner = StringScanner.new(line)
513
+ attributes_hash, rest = balance(scanner, ?{, ?})
514
+ attributes_hash = attributes_hash[1...-1] if attributes_hash
515
+ return attributes_hash, rest
516
+ end
517
+
518
+ # Parses a line that will render as an XHTML tag, and adds the code that will
519
+ # render that tag to <tt>@precompiled</tt>.
520
+ def render_tag(line)
521
+ tag_name, attributes, attributes_hash, object_ref, action, value = parse_tag(line)
522
+
523
+ raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
524
+
525
+ preserve_tag = options[:preserve].include?(tag_name)
526
+
527
+ case action
528
+ when '/'; self_closing = xhtml?
529
+ when '~'; parse = preserve_script = true
530
+ when '='
531
+ parse = true
532
+ value = unescape_interpolation(value[1..-1].strip) if value[0] == ?=
533
+ when '&', '!'
534
+ if value[0] == ?=
535
+ parse = true
536
+ value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
537
+ end
538
+ end
539
+
540
+ if parse && @options[:suppress_eval]
541
+ parse = false
542
+ value = ''
543
+ end
544
+
545
+ escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
546
+
547
+ object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
548
+
549
+ static_attributes = parse_static_hash(attributes_hash) # Try pre-compiling a static attributes hash
550
+ attributes_hash = nil if static_attributes || @options[:suppress_eval]
551
+ attributes = parse_class_and_id(attributes)
552
+ Buffer.merge_attrs(attributes, static_attributes) if static_attributes
553
+
554
+ raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", 1) if @block_opened && self_closing
555
+ raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", 1) if @block_opened && !value.empty?
556
+ raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.") if parse && value.empty?
557
+ raise SyntaxError.new("Self-closing tags can't have content.") if self_closing && !value.empty?
558
+
559
+ self_closing ||= !!( !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) )
560
+
561
+ if object_ref == "nil" && attributes_hash.nil? && !preserve_script
562
+ # This means that we can render the tag directly to text and not process it in the buffer
563
+ tag_closed = !value.empty? && !parse
564
+
565
+ open_tag = prerender_tag(tag_name, self_closing, attributes)
566
+ open_tag << "#{value}</#{tag_name}>" if tag_closed
567
+ open_tag << "\n" unless parse
568
+
569
+ push_merged_text(open_tag, tag_closed || self_closing ? 0 : 1, parse)
570
+ return if tag_closed
571
+ else
572
+ flush_merged_text
573
+ content = value.empty? || parse ? 'nil' : value.dump
574
+ attributes_hash = ', ' + attributes_hash if attributes_hash
575
+ push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{self_closing.inspect}, #{(!value.empty?).inspect}, #{preserve_tag.inspect}, #{escape_html.inspect}, #{attributes.inspect}, #{object_ref}, #{content}#{attributes_hash})"
576
+ end
577
+
578
+ return if self_closing
579
+
580
+ if value.empty?
581
+ push_and_tabulate([:element, tag_name])
582
+ @output_tabs += 1
583
+ return
584
+ end
585
+
586
+ if parse
587
+ flush_merged_text
588
+ push_script(value, preserve_script, tag_name, preserve_tag, escape_html)
589
+ end
590
+ end
591
+
592
+ # Renders a line that creates an XHTML tag and has an implicit div because of
593
+ # <tt>.</tt> or <tt>#</tt>.
594
+ def render_div(line)
595
+ render_tag('%div' + line)
596
+ end
597
+
598
+ # Renders an XHTML comment.
599
+ def render_comment(line)
600
+ conditional, line = balance(line, ?[, ?]) if line[0] == ?[
601
+ line.strip!
602
+ conditional << ">" if conditional
603
+
604
+ if @block_opened && !line.empty?
605
+ raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', 1)
606
+ end
607
+
608
+ open = "<!--#{conditional} "
609
+
610
+ # Render it statically if possible
611
+ unless line.empty?
612
+ return push_text("#{open}#{line} #{conditional ? "<![endif]-->" : "-->"}")
613
+ end
614
+
615
+ push_text(open, 1)
616
+ @output_tabs += 1
617
+ push_and_tabulate([:comment, !conditional.nil?])
618
+ unless line.empty?
619
+ push_text(line)
620
+ close
621
+ end
622
+ end
623
+
624
+ # Renders an XHTML doctype or XML shebang.
625
+ def render_doctype(line)
626
+ raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", 1) if @block_opened
627
+ doctype = text_for_doctype(line)
628
+ push_text doctype if doctype
629
+ end
630
+
631
+ def text_for_doctype(text)
632
+ text = text[3..-1].lstrip.downcase
633
+ if text.index("xml") == 0
634
+ return nil if html?
635
+ wrapper = @options[:attr_wrapper]
636
+ return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
637
+ end
638
+
639
+ if html5?
640
+ '<!DOCTYPE html>'
641
+ else
642
+ version, type = text.scan(DOCTYPE_REGEX)[0]
643
+
644
+ if xhtml?
645
+ if version == "1.1"
646
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
647
+ else
648
+ case type
649
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
650
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
651
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
652
+ end
653
+ end
654
+
655
+ elsif html4?
656
+ case type
657
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
658
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
659
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
660
+ end
661
+ end
662
+ end
663
+ end
664
+
665
+ # Starts a filtered block.
666
+ def start_filtered(name)
667
+ raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
668
+
669
+ unless filter = options[:filters][name]
670
+ if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
671
+ raise Error.new("You must have the RedCloth gem installed to use \"#{name}\" filter")
672
+ end
673
+ raise Error.new("Filter \"#{name}\" is not defined.")
674
+ end
675
+
676
+ push_and_tabulate([:filtered, filter])
677
+ @flat_spaces = @template_tabs * 2
678
+ @filter_buffer = String.new
679
+ @block_opened = false
680
+ end
681
+
682
+ def contains_interpolation?(str)
683
+ str.include?('#{')
684
+ end
685
+
686
+ def unescape_interpolation(str)
687
+ scan = StringScanner.new(str.dump)
688
+ str = ''
689
+
690
+ while scan.scan(/(.*?)(\\+)\#\{/)
691
+ escapes = (scan[2].size - 1) / 2
692
+ str << scan.matched[0...-3 - escapes]
693
+ if escapes % 2 == 1
694
+ str << '#{'
695
+ else
696
+ # Use eval to get rid of string escapes
697
+ str << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"
698
+ end
699
+ end
700
+
701
+ str + scan.rest
702
+ end
703
+
704
+ def balance(scanner, start, finish, count = 0)
705
+ str = ''
706
+ scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
707
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]")
708
+ while scanner.scan(regexp)
709
+ str << scanner.matched
710
+ count += 1 if scanner.matched[-1] == start
711
+ count -= 1 if scanner.matched[-1] == finish
712
+ return [str.strip, scanner.rest] if count == 0
713
+ end
714
+
715
+ raise SyntaxError.new("Unbalanced brackets.")
716
+ end
717
+
718
+ # Counts the tabulation of a line.
719
+ def count_soft_tabs(line)
720
+ spaces = line.index(/([^ ]|$)/)
721
+ if line[spaces] == ?\t
722
+ return nil if line.strip.empty?
723
+ raise SyntaxError.new(<<END.strip, 2)
724
+ A tab character was used for indentation. Haml must be indented using two spaces.
725
+ Are you sure you have soft tabs enabled in your editor?
726
+ END
727
+ end
728
+ [spaces, spaces/2]
729
+ end
730
+
731
+ # Pushes value onto <tt>@to_close_stack</tt> and increases
732
+ # <tt>@template_tabs</tt>.
733
+ def push_and_tabulate(value)
734
+ @to_close_stack.push(value)
735
+ @template_tabs += 1
736
+ end
737
+
738
+ def flat?
739
+ @flat_spaces != -1
740
+ end
741
+
742
+ def newline
743
+ @newlines += 1
744
+ end
745
+
746
+ def newline_now
747
+ @precompiled << "\n"
748
+ @newlines -= 1
749
+ end
750
+
751
+ def resolve_newlines
752
+ return unless @newlines > 0
753
+ @precompiled << "\n" * @newlines
754
+ @newlines = 0
755
+ end
756
+ end
757
+ end