hamlit 2.9.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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +45 -0
- data/CHANGELOG.md +676 -0
- data/Gemfile +28 -0
- data/LICENSE.txt +44 -0
- data/README.md +150 -0
- data/REFERENCE.md +266 -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/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 +553 -0
- data/ext/hamlit/hescape.c +108 -0
- data/ext/hamlit/hescape.h +20 -0
- data/hamlit.gemspec +45 -0
- data/lib/hamlit.rb +11 -0
- data/lib/hamlit/attribute_builder.rb +173 -0
- data/lib/hamlit/attribute_compiler.rb +123 -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 +102 -0
- data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
- data/lib/hamlit/compiler/tag_compiler.rb +74 -0
- data/lib/hamlit/engine.rb +37 -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 +88 -0
- data/lib/hamlit/template.rb +28 -0
- data/lib/hamlit/utils.rb +18 -0
- data/lib/hamlit/version.rb +4 -0
- metadata +361 -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
|