hamlit 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +47 -0
- data/CHANGELOG.md +702 -0
- data/Gemfile +30 -0
- data/LICENSE.txt +44 -0
- data/README.md +150 -0
- data/REFERENCE.md +272 -0
- data/Rakefile +117 -0
- data/benchmark/boolean_attribute.haml +6 -0
- data/benchmark/class_attribute.haml +5 -0
- data/benchmark/common_attribute.haml +3 -0
- data/benchmark/data_attribute.haml +4 -0
- data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
- data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
- data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
- data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
- data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
- data/benchmark/dynamic_boolean_attribute.haml +4 -0
- data/benchmark/dynamic_merger/benchmark.rb +25 -0
- data/benchmark/dynamic_merger/hello.haml +50 -0
- data/benchmark/dynamic_merger/hello.string +50 -0
- data/benchmark/etc/attribute_builder.haml +5 -0
- data/benchmark/etc/real_sample.haml +888 -0
- data/benchmark/etc/real_sample.rb +11 -0
- data/benchmark/etc/static_analyzer.haml +1 -0
- data/benchmark/etc/string_interpolation.haml +2 -0
- data/benchmark/etc/tags.haml +3 -0
- data/benchmark/etc/tags_loop.haml +2 -0
- data/benchmark/ext/build_data.rb +17 -0
- data/benchmark/ext/build_id.rb +13 -0
- data/benchmark/id_attribute.haml +3 -0
- data/benchmark/plain.haml +4 -0
- data/benchmark/script.haml +4 -0
- data/benchmark/slim/LICENSE +21 -0
- data/benchmark/slim/context.rb +11 -0
- data/benchmark/slim/run-benchmarks.rb +94 -0
- data/benchmark/slim/view.erb +23 -0
- data/benchmark/slim/view.haml +18 -0
- data/benchmark/slim/view.slim +17 -0
- data/benchmark/utils/benchmark_ips_extension.rb +43 -0
- data/bin/bench +77 -0
- data/bin/console +11 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/bin/test +24 -0
- data/exe/hamlit +6 -0
- data/ext/hamlit/extconf.rb +10 -0
- data/ext/hamlit/hamlit.c +555 -0
- data/ext/hamlit/hescape.c +108 -0
- data/ext/hamlit/hescape.h +20 -0
- data/hamlit.gemspec +47 -0
- data/lib/hamlit.rb +11 -0
- data/lib/hamlit/attribute_builder.rb +175 -0
- data/lib/hamlit/attribute_compiler.rb +125 -0
- data/lib/hamlit/attribute_parser.rb +110 -0
- data/lib/hamlit/cli.rb +130 -0
- data/lib/hamlit/compiler.rb +97 -0
- data/lib/hamlit/compiler/children_compiler.rb +112 -0
- data/lib/hamlit/compiler/comment_compiler.rb +36 -0
- data/lib/hamlit/compiler/doctype_compiler.rb +46 -0
- data/lib/hamlit/compiler/script_compiler.rb +106 -0
- data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
- data/lib/hamlit/compiler/tag_compiler.rb +76 -0
- data/lib/hamlit/dynamic_merger.rb +67 -0
- data/lib/hamlit/engine.rb +38 -0
- data/lib/hamlit/error.rb +15 -0
- data/lib/hamlit/escapable.rb +13 -0
- data/lib/hamlit/filters.rb +75 -0
- data/lib/hamlit/filters/base.rb +12 -0
- data/lib/hamlit/filters/cdata.rb +20 -0
- data/lib/hamlit/filters/coffee.rb +17 -0
- data/lib/hamlit/filters/css.rb +33 -0
- data/lib/hamlit/filters/erb.rb +10 -0
- data/lib/hamlit/filters/escaped.rb +22 -0
- data/lib/hamlit/filters/javascript.rb +33 -0
- data/lib/hamlit/filters/less.rb +20 -0
- data/lib/hamlit/filters/markdown.rb +10 -0
- data/lib/hamlit/filters/plain.rb +29 -0
- data/lib/hamlit/filters/preserve.rb +22 -0
- data/lib/hamlit/filters/ruby.rb +10 -0
- data/lib/hamlit/filters/sass.rb +15 -0
- data/lib/hamlit/filters/scss.rb +15 -0
- data/lib/hamlit/filters/text_base.rb +25 -0
- data/lib/hamlit/filters/tilt_base.rb +49 -0
- data/lib/hamlit/force_escapable.rb +29 -0
- data/lib/hamlit/helpers.rb +15 -0
- data/lib/hamlit/html.rb +14 -0
- data/lib/hamlit/identity.rb +13 -0
- data/lib/hamlit/object_ref.rb +30 -0
- data/lib/hamlit/parser.rb +49 -0
- data/lib/hamlit/parser/MIT-LICENSE +20 -0
- data/lib/hamlit/parser/README.md +30 -0
- data/lib/hamlit/parser/haml_buffer.rb +348 -0
- data/lib/hamlit/parser/haml_compiler.rb +553 -0
- data/lib/hamlit/parser/haml_error.rb +61 -0
- data/lib/hamlit/parser/haml_helpers.rb +727 -0
- data/lib/hamlit/parser/haml_options.rb +286 -0
- data/lib/hamlit/parser/haml_parser.rb +800 -0
- data/lib/hamlit/parser/haml_util.rb +288 -0
- data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
- data/lib/hamlit/rails_helpers.rb +51 -0
- data/lib/hamlit/rails_template.rb +59 -0
- data/lib/hamlit/railtie.rb +10 -0
- data/lib/hamlit/ruby_expression.rb +32 -0
- data/lib/hamlit/string_splitter.rb +19 -0
- data/lib/hamlit/template.rb +28 -0
- data/lib/hamlit/utils.rb +20 -0
- data/lib/hamlit/version.rb +4 -0
- metadata +392 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module Hamlit
|
2
|
+
# An exception raised by Haml code.
|
3
|
+
class HamlError < StandardError
|
4
|
+
|
5
|
+
MESSAGES = {
|
6
|
+
:bad_script_indent => '"%s" is indented at wrong level: expected %d, but was at %d.',
|
7
|
+
:cant_run_filter => 'Can\'t run "%s" filter; you must require its dependencies first',
|
8
|
+
:cant_use_tabs_and_spaces => "Indentation can't use both tabs and spaces.",
|
9
|
+
:deeper_indenting => "The line was indented %d levels deeper than the previous line.",
|
10
|
+
:filter_not_defined => 'Filter "%s" is not defined.',
|
11
|
+
:gem_install_filter_deps => '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile',
|
12
|
+
:illegal_element => "Illegal element: classes and ids must have values.",
|
13
|
+
:illegal_nesting_content => "Illegal nesting: nesting within a tag that already has content is illegal.",
|
14
|
+
:illegal_nesting_header => "Illegal nesting: nesting within a header command is illegal.",
|
15
|
+
:illegal_nesting_line => "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.",
|
16
|
+
:illegal_nesting_plain => "Illegal nesting: nesting within plain text is illegal.",
|
17
|
+
:illegal_nesting_self_closing => "Illegal nesting: nesting within a self-closing tag is illegal.",
|
18
|
+
:inconsistent_indentation => "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.",
|
19
|
+
:indenting_at_start => "Indenting at the beginning of the document is illegal.",
|
20
|
+
:install_haml_contrib => 'To use the "%s" filter, please install the haml-contrib gem.',
|
21
|
+
:invalid_attribute_list => 'Invalid attribute list: %s.',
|
22
|
+
:invalid_filter_name => 'Invalid filter name ":%s".',
|
23
|
+
:invalid_tag => 'Invalid tag: "%s".',
|
24
|
+
:missing_if => 'Got "%s" with no preceding "if"',
|
25
|
+
:no_ruby_code => "There's no Ruby code for %s to evaluate.",
|
26
|
+
:self_closing_content => "Self-closing tags can't have content.",
|
27
|
+
:unbalanced_brackets => 'Unbalanced brackets.',
|
28
|
+
:no_end => <<-END
|
29
|
+
You don't need to use "- end" in Haml. Un-indent to close a block:
|
30
|
+
- if foo?
|
31
|
+
%strong Foo!
|
32
|
+
- else
|
33
|
+
Not foo.
|
34
|
+
%p This line is un-indented, so it isn't part of the "if" block
|
35
|
+
END
|
36
|
+
}
|
37
|
+
|
38
|
+
def self.message(key, *args)
|
39
|
+
string = MESSAGES[key] or raise "[HAML BUG] No error messages for #{key}"
|
40
|
+
(args.empty? ? string : string % args).rstrip
|
41
|
+
end
|
42
|
+
|
43
|
+
# The line of the template on which the error occurred.
|
44
|
+
#
|
45
|
+
# @return [Fixnum]
|
46
|
+
attr_reader :line
|
47
|
+
|
48
|
+
# @param message [String] The error message
|
49
|
+
# @param line [Fixnum] See \{#line}
|
50
|
+
def initialize(message = nil, line = nil)
|
51
|
+
super(message)
|
52
|
+
@line = line
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# SyntaxError is the type of exception raised when Haml encounters an
|
57
|
+
# ill-formatted document.
|
58
|
+
# It's not particularly interesting,
|
59
|
+
# except in that it's a subclass of {Haml::Error}.
|
60
|
+
class HamlSyntaxError < HamlError; end
|
61
|
+
end
|
@@ -0,0 +1,727 @@
|
|
1
|
+
require 'hamlit/parser/haml_error'
|
2
|
+
require 'hamlit/parser/haml_options'
|
3
|
+
require 'hamlit/parser/haml_compiler'
|
4
|
+
require 'hamlit/parser/haml_parser'
|
5
|
+
|
6
|
+
module Hamlit
|
7
|
+
# This module contains various helpful methods to make it easier to do various tasks.
|
8
|
+
# {Haml::Helpers} is automatically included in the context
|
9
|
+
# that a Haml template is parsed in, so all these methods are at your
|
10
|
+
# disposal from within the template.
|
11
|
+
module HamlHelpers
|
12
|
+
# An object that raises an error when \{#to\_s} is called.
|
13
|
+
# It's used to raise an error when the return value of a helper is used
|
14
|
+
# when it shouldn't be.
|
15
|
+
class ErrorReturn
|
16
|
+
def initialize(method)
|
17
|
+
@message = <<MESSAGE
|
18
|
+
#{method} outputs directly to the Haml template.
|
19
|
+
Disregard its return value and use the - operator,
|
20
|
+
or use capture_haml to get the value as a String.
|
21
|
+
MESSAGE
|
22
|
+
end
|
23
|
+
|
24
|
+
# Raises an error.
|
25
|
+
#
|
26
|
+
# @raise [Haml::Error] The error
|
27
|
+
def to_s
|
28
|
+
raise ::Hamlit::HamlError.new(@message)
|
29
|
+
rescue ::Hamlit::HamlError => e
|
30
|
+
e.backtrace.shift
|
31
|
+
|
32
|
+
# If the ErrorReturn is used directly in the template,
|
33
|
+
# we don't want Haml's stuff to get into the backtrace,
|
34
|
+
# so we get rid of the format_script line.
|
35
|
+
#
|
36
|
+
# We also have to subtract one from the Haml line number
|
37
|
+
# since the value is passed to format_script the line after
|
38
|
+
# it's actually used.
|
39
|
+
if e.backtrace.first =~ /^\(eval\):\d+:in `format_script/
|
40
|
+
e.backtrace.shift
|
41
|
+
e.backtrace.first.gsub!(/^\(haml\):(\d+)/) {|s| "(haml):#{$1.to_i - 1}"}
|
42
|
+
end
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [String] A human-readable string representation
|
47
|
+
def inspect
|
48
|
+
"::Hamlit::HamlHelpers::ErrorReturn(#{@message.inspect})"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
self.extend self
|
53
|
+
|
54
|
+
@@action_view_defined = false
|
55
|
+
|
56
|
+
# @return [Boolean] Whether or not ActionView is loaded
|
57
|
+
def self.action_view?
|
58
|
+
@@action_view_defined
|
59
|
+
end
|
60
|
+
|
61
|
+
# Note: this does **not** need to be called when using Haml helpers
|
62
|
+
# normally in Rails.
|
63
|
+
#
|
64
|
+
# Initializes the current object as though it were in the same context
|
65
|
+
# as a normal ActionView instance using Haml.
|
66
|
+
# This is useful if you want to use the helpers in a context
|
67
|
+
# other than the normal setup with ActionView.
|
68
|
+
# For example:
|
69
|
+
#
|
70
|
+
# context = Object.new
|
71
|
+
# class << context
|
72
|
+
# include Haml::Helpers
|
73
|
+
# end
|
74
|
+
# context.init_haml_helpers
|
75
|
+
# context.haml_tag :p, "Stuff"
|
76
|
+
#
|
77
|
+
def init_haml_helpers
|
78
|
+
@haml_buffer = ::Hamlit::HamlBuffer.new(haml_buffer, ::Hamlit::HamlOptions.new.for_buffer)
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
# Runs a block of code in a non-Haml context
|
83
|
+
# (i.e. \{#is\_haml?} will return false).
|
84
|
+
#
|
85
|
+
# This is mainly useful for rendering sub-templates such as partials in a non-Haml language,
|
86
|
+
# particularly where helpers may behave differently when run from Haml.
|
87
|
+
#
|
88
|
+
# Note that this is automatically applied to Rails partials.
|
89
|
+
#
|
90
|
+
# @yield A block which won't register as Haml
|
91
|
+
def non_haml
|
92
|
+
was_active = @haml_buffer.active?
|
93
|
+
@haml_buffer.active = false
|
94
|
+
yield
|
95
|
+
ensure
|
96
|
+
@haml_buffer.active = was_active
|
97
|
+
end
|
98
|
+
|
99
|
+
# Uses \{#preserve} to convert any newlines inside whitespace-sensitive tags
|
100
|
+
# into the HTML entities for endlines.
|
101
|
+
#
|
102
|
+
# @param tags [Array<String>] Tags that should have newlines escaped
|
103
|
+
#
|
104
|
+
# @overload find_and_preserve(input, tags = haml_buffer.options[:preserve])
|
105
|
+
# Escapes newlines within a string.
|
106
|
+
#
|
107
|
+
# @param input [String] The string within which to escape newlines
|
108
|
+
# @overload find_and_preserve(tags = haml_buffer.options[:preserve])
|
109
|
+
# Escapes newlines within a block of Haml code.
|
110
|
+
#
|
111
|
+
# @yield The block within which to escape newlines
|
112
|
+
def find_and_preserve(input = nil, tags = haml_buffer.options[:preserve], &block)
|
113
|
+
return find_and_preserve(capture_haml(&block), input || tags) if block
|
114
|
+
tags = tags.each_with_object('') do |t, s|
|
115
|
+
s << '|' unless s.empty?
|
116
|
+
s << Regexp.escape(t)
|
117
|
+
end
|
118
|
+
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
119
|
+
input.to_s.gsub(re) do |s|
|
120
|
+
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
121
|
+
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Takes any string, finds all the newlines, and converts them to
|
126
|
+
# HTML entities so they'll render correctly in
|
127
|
+
# whitespace-sensitive tags without screwing up the indentation.
|
128
|
+
#
|
129
|
+
# @overload preserve(input)
|
130
|
+
# Escapes newlines within a string.
|
131
|
+
#
|
132
|
+
# @param input [String] The string within which to escape all newlines
|
133
|
+
# @overload preserve
|
134
|
+
# Escapes newlines within a block of Haml code.
|
135
|
+
#
|
136
|
+
# @yield The block within which to escape newlines
|
137
|
+
def preserve(input = nil, &block)
|
138
|
+
return preserve(capture_haml(&block)) if block
|
139
|
+
s = input.to_s.chomp("\n")
|
140
|
+
s.gsub!(/\n/, '
')
|
141
|
+
s.delete!("\r")
|
142
|
+
s
|
143
|
+
end
|
144
|
+
alias_method :flatten, :preserve
|
145
|
+
|
146
|
+
# Takes an `Enumerable` object and a block
|
147
|
+
# and iterates over the enum,
|
148
|
+
# yielding each element to a Haml block
|
149
|
+
# and putting the result into `<li>` elements.
|
150
|
+
# This creates a list of the results of the block.
|
151
|
+
# For example:
|
152
|
+
#
|
153
|
+
# = list_of([['hello'], ['yall']]) do |i|
|
154
|
+
# = i[0]
|
155
|
+
#
|
156
|
+
# Produces:
|
157
|
+
#
|
158
|
+
# <li>hello</li>
|
159
|
+
# <li>yall</li>
|
160
|
+
#
|
161
|
+
# And:
|
162
|
+
#
|
163
|
+
# = list_of({:title => 'All the stuff', :description => 'A book about all the stuff.'}) do |key, val|
|
164
|
+
# %h3= key.humanize
|
165
|
+
# %p= val
|
166
|
+
#
|
167
|
+
# Produces:
|
168
|
+
#
|
169
|
+
# <li>
|
170
|
+
# <h3>Title</h3>
|
171
|
+
# <p>All the stuff</p>
|
172
|
+
# </li>
|
173
|
+
# <li>
|
174
|
+
# <h3>Description</h3>
|
175
|
+
# <p>A book about all the stuff.</p>
|
176
|
+
# </li>
|
177
|
+
#
|
178
|
+
# While:
|
179
|
+
#
|
180
|
+
# = list_of(["Home", "About", "Contact", "FAQ"], {class: "nav", role: "nav"}) do |item|
|
181
|
+
# %a{ href="#" }= item
|
182
|
+
#
|
183
|
+
# Produces:
|
184
|
+
#
|
185
|
+
# <li class='nav' role='nav'>
|
186
|
+
# <a href='#'>Home</a>
|
187
|
+
# </li>
|
188
|
+
# <li class='nav' role='nav'>
|
189
|
+
# <a href='#'>About</a>
|
190
|
+
# </li>
|
191
|
+
# <li class='nav' role='nav'>
|
192
|
+
# <a href='#'>Contact</a>
|
193
|
+
# </li>
|
194
|
+
# <li class='nav' role='nav'>
|
195
|
+
# <a href='#'>FAQ</a>
|
196
|
+
# </li>
|
197
|
+
#
|
198
|
+
# `[[class", "nav"], [role", "nav"]]` could have been used instead of `{class: "nav", role: "nav"}` (or any enumerable collection where each pair of items responds to #to_s)
|
199
|
+
#
|
200
|
+
# @param enum [Enumerable] The list of objects to iterate over
|
201
|
+
# @param [Enumerable<#to_s,#to_s>] opts Each key/value pair will become an attribute pair for each list item element.
|
202
|
+
# @yield [item] A block which contains Haml code that goes within list items
|
203
|
+
# @yieldparam item An element of `enum`
|
204
|
+
def list_of(enum, opts={}, &block)
|
205
|
+
opts_attributes = opts.each_with_object('') {|(k, v), s| s << " #{k}='#{v}'"}
|
206
|
+
enum.each_with_object('') do |i, ret|
|
207
|
+
result = capture_haml(i, &block)
|
208
|
+
|
209
|
+
if result.count("\n") > 1
|
210
|
+
result.gsub!("\n", "\n ")
|
211
|
+
result = "\n #{result.strip!}\n"
|
212
|
+
else
|
213
|
+
result.strip!
|
214
|
+
end
|
215
|
+
|
216
|
+
ret << "\n" unless ret.empty?
|
217
|
+
ret << %Q!<li#{opts_attributes}>#{result}</li>!
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns a hash containing default assignments for the `xmlns`, `lang`, and `xml:lang`
|
222
|
+
# attributes of the `html` HTML element.
|
223
|
+
# For example,
|
224
|
+
#
|
225
|
+
# %html{html_attrs}
|
226
|
+
#
|
227
|
+
# becomes
|
228
|
+
#
|
229
|
+
# <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en-US' lang='en-US'>
|
230
|
+
#
|
231
|
+
# @param lang [String] The value of `xml:lang` and `lang`
|
232
|
+
# @return [{#to_s => String}] The attribute hash
|
233
|
+
def html_attrs(lang = 'en-US')
|
234
|
+
if haml_buffer.options[:format] == :xhtml
|
235
|
+
{:xmlns => "http://www.w3.org/1999/xhtml", 'xml:lang' => lang, :lang => lang}
|
236
|
+
else
|
237
|
+
{:lang => lang}
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Increments the number of tabs the buffer automatically adds
|
242
|
+
# to the lines of the template.
|
243
|
+
# For example:
|
244
|
+
#
|
245
|
+
# %h1 foo
|
246
|
+
# - tab_up
|
247
|
+
# %p bar
|
248
|
+
# - tab_down
|
249
|
+
# %strong baz
|
250
|
+
#
|
251
|
+
# Produces:
|
252
|
+
#
|
253
|
+
# <h1>foo</h1>
|
254
|
+
# <p>bar</p>
|
255
|
+
# <strong>baz</strong>
|
256
|
+
#
|
257
|
+
# @param i [Fixnum] The number of tabs by which to increase the indentation
|
258
|
+
# @see #tab_down
|
259
|
+
def tab_up(i = 1)
|
260
|
+
haml_buffer.tabulation += i
|
261
|
+
end
|
262
|
+
|
263
|
+
# Decrements the number of tabs the buffer automatically adds
|
264
|
+
# to the lines of the template.
|
265
|
+
#
|
266
|
+
# @param i [Fixnum] The number of tabs by which to decrease the indentation
|
267
|
+
# @see #tab_up
|
268
|
+
def tab_down(i = 1)
|
269
|
+
haml_buffer.tabulation -= i
|
270
|
+
end
|
271
|
+
|
272
|
+
# Sets the number of tabs the buffer automatically adds
|
273
|
+
# to the lines of the template,
|
274
|
+
# but only for the duration of the block.
|
275
|
+
# For example:
|
276
|
+
#
|
277
|
+
# %h1 foo
|
278
|
+
# - with_tabs(2) do
|
279
|
+
# %p bar
|
280
|
+
# %strong baz
|
281
|
+
#
|
282
|
+
# Produces:
|
283
|
+
#
|
284
|
+
# <h1>foo</h1>
|
285
|
+
# <p>bar</p>
|
286
|
+
# <strong>baz</strong>
|
287
|
+
#
|
288
|
+
#
|
289
|
+
# @param i [Fixnum] The number of tabs to use
|
290
|
+
# @yield A block in which the indentation will be `i` spaces
|
291
|
+
def with_tabs(i)
|
292
|
+
old_tabs = haml_buffer.tabulation
|
293
|
+
haml_buffer.tabulation = i
|
294
|
+
yield
|
295
|
+
ensure
|
296
|
+
haml_buffer.tabulation = old_tabs
|
297
|
+
end
|
298
|
+
|
299
|
+
# Surrounds a block of Haml code with strings,
|
300
|
+
# with no whitespace in between.
|
301
|
+
# For example:
|
302
|
+
#
|
303
|
+
# = surround '(', ')' do
|
304
|
+
# %a{:href => "food"} chicken
|
305
|
+
#
|
306
|
+
# Produces:
|
307
|
+
#
|
308
|
+
# (<a href='food'>chicken</a>)
|
309
|
+
#
|
310
|
+
# and
|
311
|
+
#
|
312
|
+
# = surround '*' do
|
313
|
+
# %strong angry
|
314
|
+
#
|
315
|
+
# Produces:
|
316
|
+
#
|
317
|
+
# *<strong>angry</strong>*
|
318
|
+
#
|
319
|
+
# @param front [String] The string to add before the Haml
|
320
|
+
# @param back [String] The string to add after the Haml
|
321
|
+
# @yield A block of Haml to surround
|
322
|
+
def surround(front, back = front, &block)
|
323
|
+
output = capture_haml(&block)
|
324
|
+
|
325
|
+
"#{front}#{output.chomp}#{back}\n"
|
326
|
+
end
|
327
|
+
|
328
|
+
# Prepends a string to the beginning of a Haml block,
|
329
|
+
# with no whitespace between.
|
330
|
+
# For example:
|
331
|
+
#
|
332
|
+
# = precede '*' do
|
333
|
+
# %span.small Not really
|
334
|
+
#
|
335
|
+
# Produces:
|
336
|
+
#
|
337
|
+
# *<span class='small'>Not really</span>
|
338
|
+
#
|
339
|
+
# @param str [String] The string to add before the Haml
|
340
|
+
# @yield A block of Haml to prepend to
|
341
|
+
def precede(str, &block)
|
342
|
+
"#{str}#{capture_haml(&block).chomp}\n"
|
343
|
+
end
|
344
|
+
|
345
|
+
# Appends a string to the end of a Haml block,
|
346
|
+
# with no whitespace between.
|
347
|
+
# For example:
|
348
|
+
#
|
349
|
+
# click
|
350
|
+
# = succeed '.' do
|
351
|
+
# %a{:href=>"thing"} here
|
352
|
+
#
|
353
|
+
# Produces:
|
354
|
+
#
|
355
|
+
# click
|
356
|
+
# <a href='thing'>here</a>.
|
357
|
+
#
|
358
|
+
# @param str [String] The string to add after the Haml
|
359
|
+
# @yield A block of Haml to append to
|
360
|
+
def succeed(str, &block)
|
361
|
+
"#{capture_haml(&block).chomp}#{str}\n"
|
362
|
+
end
|
363
|
+
|
364
|
+
# Captures the result of a block of Haml code,
|
365
|
+
# gets rid of the excess indentation,
|
366
|
+
# and returns it as a string.
|
367
|
+
# For example, after the following,
|
368
|
+
#
|
369
|
+
# .foo
|
370
|
+
# - foo = capture_haml(13) do |a|
|
371
|
+
# %p= a
|
372
|
+
#
|
373
|
+
# the local variable `foo` would be assigned to `"<p>13</p>\n"`.
|
374
|
+
#
|
375
|
+
# @param args [Array] Arguments to pass into the block
|
376
|
+
# @yield [args] A block of Haml code that will be converted to a string
|
377
|
+
# @yieldparam args [Array] `args`
|
378
|
+
def capture_haml(*args, &block)
|
379
|
+
buffer = eval('if defined? _hamlout then _hamlout else nil end', block.binding) || haml_buffer
|
380
|
+
with_haml_buffer(buffer) do
|
381
|
+
position = haml_buffer.buffer.length
|
382
|
+
|
383
|
+
haml_buffer.capture_position = position
|
384
|
+
value = block.call(*args)
|
385
|
+
|
386
|
+
captured = haml_buffer.buffer.slice!(position..-1)
|
387
|
+
|
388
|
+
if captured == '' and value != haml_buffer.buffer
|
389
|
+
captured = (value.is_a?(String) ? value : nil)
|
390
|
+
end
|
391
|
+
|
392
|
+
return nil if captured.nil?
|
393
|
+
return (haml_buffer.options[:ugly] ? captured : prettify(captured))
|
394
|
+
end
|
395
|
+
ensure
|
396
|
+
haml_buffer.capture_position = nil
|
397
|
+
end
|
398
|
+
|
399
|
+
# Outputs text directly to the Haml buffer, with the proper indentation.
|
400
|
+
#
|
401
|
+
# @param text [#to_s] The text to output
|
402
|
+
def haml_concat(text = "")
|
403
|
+
haml_internal_concat text
|
404
|
+
ErrorReturn.new("haml_concat")
|
405
|
+
end
|
406
|
+
|
407
|
+
# Internal method to write directly to the buffer with control of
|
408
|
+
# whether the first line should be indented, and if there should be a
|
409
|
+
# final newline.
|
410
|
+
#
|
411
|
+
# Lines added will have the proper indentation. This can be controlled
|
412
|
+
# for the first line.
|
413
|
+
#
|
414
|
+
# Used by #haml_concat and #haml_tag.
|
415
|
+
#
|
416
|
+
# @param text [#to_s] The text to output
|
417
|
+
# @param newline [Boolean] Whether to add a newline after the text
|
418
|
+
# @param indent [Boolean] Whether to add indentation to the first line
|
419
|
+
def haml_internal_concat(text = "", newline = true, indent = true)
|
420
|
+
if haml_buffer.options[:ugly] || haml_buffer.tabulation == 0
|
421
|
+
haml_buffer.buffer << "#{text}#{"\n" if newline}"
|
422
|
+
else
|
423
|
+
haml_buffer.buffer << %[#{haml_indent if indent}#{text.to_s.gsub("\n", "\n#{haml_indent}")}#{"\n" if newline}]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
private :haml_internal_concat
|
427
|
+
|
428
|
+
# Allows writing raw content. `haml_internal_concat_raw` isn't
|
429
|
+
# effected by XSS mods. Used by #haml_tag to write the actual tags.
|
430
|
+
alias :haml_internal_concat_raw :haml_internal_concat
|
431
|
+
|
432
|
+
# @return [String] The indentation string for the current line
|
433
|
+
def haml_indent
|
434
|
+
' ' * haml_buffer.tabulation
|
435
|
+
end
|
436
|
+
|
437
|
+
# Creates an HTML tag with the given name and optionally text and attributes.
|
438
|
+
# Can take a block that will run between the opening and closing tags.
|
439
|
+
# If the block is a Haml block or outputs text using \{#haml\_concat},
|
440
|
+
# the text will be properly indented.
|
441
|
+
#
|
442
|
+
# `name` can be a string using the standard Haml class/id shorthand
|
443
|
+
# (e.g. "span#foo.bar", "#foo").
|
444
|
+
# Just like standard Haml tags, these class and id values
|
445
|
+
# will be merged with manually-specified attributes.
|
446
|
+
#
|
447
|
+
# `flags` is a list of symbol flags
|
448
|
+
# like those that can be put at the end of a Haml tag
|
449
|
+
# (`:/`, `:<`, and `:>`).
|
450
|
+
# Currently, only `:/` and `:<` are supported.
|
451
|
+
#
|
452
|
+
# `haml_tag` outputs directly to the buffer;
|
453
|
+
# its return value should not be used.
|
454
|
+
# If you need to get the results as a string,
|
455
|
+
# use \{#capture\_haml\}.
|
456
|
+
#
|
457
|
+
# For example,
|
458
|
+
#
|
459
|
+
# haml_tag :table do
|
460
|
+
# haml_tag :tr do
|
461
|
+
# haml_tag 'td.cell' do
|
462
|
+
# haml_tag :strong, "strong!"
|
463
|
+
# haml_concat "data"
|
464
|
+
# end
|
465
|
+
# haml_tag :td do
|
466
|
+
# haml_concat "more_data"
|
467
|
+
# end
|
468
|
+
# end
|
469
|
+
# end
|
470
|
+
#
|
471
|
+
# outputs
|
472
|
+
#
|
473
|
+
# <table>
|
474
|
+
# <tr>
|
475
|
+
# <td class='cell'>
|
476
|
+
# <strong>
|
477
|
+
# strong!
|
478
|
+
# </strong>
|
479
|
+
# data
|
480
|
+
# </td>
|
481
|
+
# <td>
|
482
|
+
# more_data
|
483
|
+
# </td>
|
484
|
+
# </tr>
|
485
|
+
# </table>
|
486
|
+
#
|
487
|
+
# @param name [#to_s] The name of the tag
|
488
|
+
#
|
489
|
+
# @overload haml_tag(name, *rest, attributes = {})
|
490
|
+
# @yield The block of Haml code within the tag
|
491
|
+
# @overload haml_tag(name, text, *flags, attributes = {})
|
492
|
+
# @param text [#to_s] The text within the tag
|
493
|
+
# @param flags [Array<Symbol>] Haml end-of-tag flags
|
494
|
+
def haml_tag(name, *rest, &block)
|
495
|
+
ret = ErrorReturn.new("haml_tag")
|
496
|
+
|
497
|
+
text = rest.shift.to_s unless [Symbol, Hash, NilClass].any? {|t| rest.first.is_a? t}
|
498
|
+
flags = []
|
499
|
+
flags << rest.shift while rest.first.is_a? Symbol
|
500
|
+
attrs = (rest.shift || {})
|
501
|
+
attrs.keys.each {|key| attrs[key.to_s] = attrs.delete(key)} unless attrs.empty?
|
502
|
+
name, attrs = merge_name_and_attributes(name.to_s, attrs)
|
503
|
+
|
504
|
+
attributes = ::Hamlit::HamlCompiler.build_attributes(haml_buffer.html?,
|
505
|
+
haml_buffer.options[:attr_wrapper],
|
506
|
+
haml_buffer.options[:escape_attrs],
|
507
|
+
haml_buffer.options[:hyphenate_data_attrs],
|
508
|
+
attrs)
|
509
|
+
|
510
|
+
if text.nil? && block.nil? && (haml_buffer.options[:autoclose].include?(name) || flags.include?(:/))
|
511
|
+
haml_internal_concat_raw "<#{name}#{attributes}#{' /' if haml_buffer.options[:format] == :xhtml}>"
|
512
|
+
return ret
|
513
|
+
end
|
514
|
+
|
515
|
+
if flags.include?(:/)
|
516
|
+
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:self_closing_content)) if text
|
517
|
+
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:illegal_nesting_self_closing)) if block
|
518
|
+
end
|
519
|
+
|
520
|
+
tag = "<#{name}#{attributes}>"
|
521
|
+
end_tag = "</#{name}>"
|
522
|
+
if block.nil?
|
523
|
+
text = text.to_s
|
524
|
+
if text.include?("\n")
|
525
|
+
haml_internal_concat_raw tag
|
526
|
+
tab_up
|
527
|
+
haml_internal_concat text
|
528
|
+
tab_down
|
529
|
+
haml_internal_concat_raw end_tag
|
530
|
+
else
|
531
|
+
haml_internal_concat_raw tag, false
|
532
|
+
haml_internal_concat text, false, false
|
533
|
+
haml_internal_concat_raw end_tag, true, false
|
534
|
+
end
|
535
|
+
return ret
|
536
|
+
end
|
537
|
+
|
538
|
+
if text
|
539
|
+
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:illegal_nesting_line, name))
|
540
|
+
end
|
541
|
+
|
542
|
+
if flags.include?(:<)
|
543
|
+
haml_internal_concat_raw tag, false
|
544
|
+
haml_internal_concat "#{capture_haml(&block).strip}", false, false
|
545
|
+
haml_internal_concat_raw end_tag, true, false
|
546
|
+
return ret
|
547
|
+
end
|
548
|
+
|
549
|
+
haml_internal_concat_raw tag
|
550
|
+
tab_up
|
551
|
+
block.call
|
552
|
+
tab_down
|
553
|
+
haml_internal_concat_raw end_tag
|
554
|
+
|
555
|
+
ret
|
556
|
+
end
|
557
|
+
|
558
|
+
# Conditionally wrap a block in an element. If `condition` is `true` then
|
559
|
+
# this method renders the tag described by the arguments in `tag` (using
|
560
|
+
# \{#haml_tag}) with the given block inside, otherwise it just renders the block.
|
561
|
+
#
|
562
|
+
# For example,
|
563
|
+
#
|
564
|
+
# - haml_tag_if important, '.important' do
|
565
|
+
# %p
|
566
|
+
# A (possibly) important paragraph.
|
567
|
+
#
|
568
|
+
# will produce
|
569
|
+
#
|
570
|
+
# <div class='important'>
|
571
|
+
# <p>
|
572
|
+
# A (possibly) important paragraph.
|
573
|
+
# </p>
|
574
|
+
# </div>
|
575
|
+
#
|
576
|
+
# if `important` is truthy, and just
|
577
|
+
#
|
578
|
+
# <p>
|
579
|
+
# A (possibly) important paragraph.
|
580
|
+
# </p>
|
581
|
+
#
|
582
|
+
# otherwise.
|
583
|
+
#
|
584
|
+
# Like \{#haml_tag}, `haml_tag_if` outputs directly to the buffer and its
|
585
|
+
# return value should not be used. Use \{#capture_haml} if you need to use
|
586
|
+
# its results as a string.
|
587
|
+
#
|
588
|
+
# @param condition The condition to test to determine whether to render
|
589
|
+
# the enclosing tag
|
590
|
+
# @param tag Definition of the enclosing tag. See \{#haml_tag} for details
|
591
|
+
# (specifically the form that takes a block)
|
592
|
+
def haml_tag_if(condition, *tag)
|
593
|
+
if condition
|
594
|
+
haml_tag(*tag){ yield }
|
595
|
+
else
|
596
|
+
yield
|
597
|
+
end
|
598
|
+
ErrorReturn.new("haml_tag_if")
|
599
|
+
end
|
600
|
+
|
601
|
+
# Characters that need to be escaped to HTML entities from user input
|
602
|
+
HTML_ESCAPE = { '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' }
|
603
|
+
|
604
|
+
HTML_ESCAPE_REGEX = /[\"><&]/
|
605
|
+
|
606
|
+
# Returns a copy of `text` with ampersands, angle brackets and quotes
|
607
|
+
# escaped into HTML entities.
|
608
|
+
#
|
609
|
+
# Note that if ActionView is loaded and XSS protection is enabled
|
610
|
+
# (as is the default for Rails 3.0+, and optional for version 2.3.5+),
|
611
|
+
# this won't escape text declared as "safe".
|
612
|
+
#
|
613
|
+
# @param text [String] The string to sanitize
|
614
|
+
# @return [String] The sanitized string
|
615
|
+
def html_escape(text)
|
616
|
+
text = text.to_s
|
617
|
+
text.gsub(HTML_ESCAPE_REGEX, HTML_ESCAPE)
|
618
|
+
end
|
619
|
+
|
620
|
+
HTML_ESCAPE_ONCE_REGEX = /[\"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
|
621
|
+
|
622
|
+
# Escapes HTML entities in `text`, but without escaping an ampersand
|
623
|
+
# that is already part of an escaped entity.
|
624
|
+
#
|
625
|
+
# @param text [String] The string to sanitize
|
626
|
+
# @return [String] The sanitized string
|
627
|
+
def escape_once(text)
|
628
|
+
text = text.to_s
|
629
|
+
text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
|
630
|
+
end
|
631
|
+
|
632
|
+
# Returns whether or not the current template is a Haml template.
|
633
|
+
#
|
634
|
+
# This function, unlike other {Haml::Helpers} functions,
|
635
|
+
# also works in other `ActionView` templates,
|
636
|
+
# where it will always return false.
|
637
|
+
#
|
638
|
+
# @return [Boolean] Whether or not the current template is a Haml template
|
639
|
+
def is_haml?
|
640
|
+
!@haml_buffer.nil? && @haml_buffer.active?
|
641
|
+
end
|
642
|
+
|
643
|
+
# Returns whether or not `block` is defined directly in a Haml template.
|
644
|
+
#
|
645
|
+
# @param block [Proc] A Ruby block
|
646
|
+
# @return [Boolean] Whether or not `block` is defined directly in a Haml template
|
647
|
+
def block_is_haml?(block)
|
648
|
+
eval('!!defined?(_hamlout)', block.binding)
|
649
|
+
end
|
650
|
+
|
651
|
+
private
|
652
|
+
|
653
|
+
# Parses the tag name used for \{#haml\_tag}
|
654
|
+
# and merges it with the Ruby attributes hash.
|
655
|
+
def merge_name_and_attributes(name, attributes_hash = {})
|
656
|
+
# skip merging if no ids or classes found in name
|
657
|
+
return name, attributes_hash unless name =~ /^(.+?)?([\.#].*)$/
|
658
|
+
|
659
|
+
return $1 || "div", ::Hamlit::HamlBuffer.merge_attrs(
|
660
|
+
::Hamlit::HamlParser.parse_class_and_id($2), attributes_hash)
|
661
|
+
end
|
662
|
+
|
663
|
+
# Runs a block of code with the given buffer as the currently active buffer.
|
664
|
+
#
|
665
|
+
# @param buffer [Haml::Buffer] The Haml buffer to use temporarily
|
666
|
+
# @yield A block in which the given buffer should be used
|
667
|
+
def with_haml_buffer(buffer)
|
668
|
+
@haml_buffer, old_buffer = buffer, @haml_buffer
|
669
|
+
old_buffer.active, old_was_active = false, old_buffer.active? if old_buffer
|
670
|
+
@haml_buffer.active, was_active = true, @haml_buffer.active?
|
671
|
+
yield
|
672
|
+
ensure
|
673
|
+
@haml_buffer.active = was_active
|
674
|
+
old_buffer.active = old_was_active if old_buffer
|
675
|
+
@haml_buffer = old_buffer
|
676
|
+
end
|
677
|
+
|
678
|
+
# The current {Haml::Buffer} object.
|
679
|
+
#
|
680
|
+
# @return [Haml::Buffer]
|
681
|
+
def haml_buffer
|
682
|
+
@haml_buffer if defined? @haml_buffer
|
683
|
+
end
|
684
|
+
|
685
|
+
# Gives a proc the same local `_hamlout` and `_erbout` variables
|
686
|
+
# that the current template has.
|
687
|
+
#
|
688
|
+
# @param proc [#call] The proc to bind
|
689
|
+
# @return [Proc] A new proc with the new variables bound
|
690
|
+
def haml_bind_proc(&proc)
|
691
|
+
_hamlout = haml_buffer
|
692
|
+
#double assignment is to avoid warnings
|
693
|
+
_erbout = _erbout = _hamlout.buffer
|
694
|
+
proc { |*args| proc.call(*args) }
|
695
|
+
end
|
696
|
+
|
697
|
+
def prettify(text)
|
698
|
+
text = text.split(/^/)
|
699
|
+
text.delete('')
|
700
|
+
|
701
|
+
min_tabs = nil
|
702
|
+
text.each do |line|
|
703
|
+
tabs = line.index(/[^ ]/) || line.length
|
704
|
+
min_tabs ||= tabs
|
705
|
+
min_tabs = min_tabs > tabs ? tabs : min_tabs
|
706
|
+
end
|
707
|
+
|
708
|
+
text.each_with_object('') do |line, str|
|
709
|
+
str << line.slice(min_tabs, line.length)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
# @private
|
716
|
+
class Object
|
717
|
+
# Haml overrides various `ActionView` helpers,
|
718
|
+
# which call an \{#is\_haml?} method
|
719
|
+
# to determine whether or not the current context object
|
720
|
+
# is a proper Haml context.
|
721
|
+
# Because `ActionView` helpers may be included in non-`ActionView::Base` classes,
|
722
|
+
# it's a good idea to define \{#is\_haml?} for all objects.
|
723
|
+
def is_haml?
|
724
|
+
false
|
725
|
+
end
|
726
|
+
alias :is_haml? :is_haml?
|
727
|
+
end
|