hamlit 1.7.2 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -3
- data/.gitmodules +3 -0
- data/.travis.yml +25 -37
- data/CHANGELOG.md +18 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +23 -2
- data/README.md +106 -48
- data/REFERENCE.md +222 -0
- data/Rakefile +77 -19
- 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/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/tags.haml +3 -0
- data/benchmark/ext/build_data.rb +15 -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/{benchmarks → benchmark/slim}/context.rb +2 -4
- data/benchmark/slim/run-benchmarks.rb +94 -0
- data/{benchmarks → benchmark/slim}/view.erb +3 -3
- data/{benchmarks → benchmark/slim}/view.haml +0 -0
- data/{benchmarks/view.escaped.slim → benchmark/slim/view.slim} +1 -1
- data/benchmark/string_interpolation.haml +2 -0
- data/benchmark/utils/benchmark_ips_extension.rb +43 -0
- data/bin/bench +85 -0
- data/bin/clone +14 -0
- data/bin/console +11 -0
- data/bin/lineprof +48 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/{test → bin/test} +6 -10
- data/{bin → exe}/hamlit +0 -0
- data/ext/hamlit/extconf.rb +14 -0
- data/ext/hamlit/hamlit.c +512 -0
- data/ext/hamlit/houdini/.gitignore +3 -0
- data/ext/hamlit/houdini/COPYING +7 -0
- data/ext/hamlit/houdini/Makefile +79 -0
- data/ext/hamlit/houdini/README.md +59 -0
- data/ext/hamlit/houdini/buffer.c +249 -0
- data/ext/hamlit/houdini/buffer.h +113 -0
- data/ext/hamlit/houdini/houdini.h +46 -0
- data/ext/hamlit/houdini/houdini_href_e.c +115 -0
- data/ext/hamlit/houdini/houdini_html_e.c +90 -0
- data/ext/hamlit/houdini/houdini_html_u.c +122 -0
- data/ext/hamlit/houdini/houdini_js_e.c +90 -0
- data/ext/hamlit/houdini/houdini_js_u.c +60 -0
- data/ext/hamlit/houdini/houdini_uri_e.c +107 -0
- data/ext/hamlit/houdini/houdini_uri_u.c +68 -0
- data/ext/hamlit/houdini/houdini_xml_e.c +136 -0
- data/ext/hamlit/houdini/html_unescape.gperf +258 -0
- data/ext/hamlit/houdini/html_unescape.h +754 -0
- data/ext/hamlit/houdini/tools/build_table.py +13 -0
- data/ext/hamlit/houdini/tools/build_tables.c +51 -0
- data/ext/hamlit/houdini/tools/wikipedia_table.txt +2025 -0
- data/hamlit.gemspec +30 -31
- data/lib/hamlit.rb +3 -1
- data/lib/hamlit/attribute_builder.rb +12 -0
- data/lib/hamlit/cli.rb +44 -43
- data/lib/hamlit/compiler.rb +92 -16
- data/lib/hamlit/compiler/attribute_compiler.rb +148 -0
- data/lib/hamlit/compiler/children_compiler.rb +111 -0
- data/lib/hamlit/compiler/comment_compiler.rb +36 -0
- data/lib/hamlit/compiler/doctype_compiler.rb +45 -0
- data/lib/hamlit/compiler/script_compiler.rb +97 -0
- data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
- data/lib/hamlit/compiler/tag_compiler.rb +69 -0
- data/lib/hamlit/engine.rb +12 -7
- data/lib/hamlit/error.rb +14 -0
- data/lib/hamlit/escapable.rb +12 -0
- data/lib/hamlit/filters.rb +65 -0
- data/lib/hamlit/filters/base.rb +4 -62
- data/lib/hamlit/filters/coffee.rb +9 -7
- data/lib/hamlit/filters/css.rb +25 -8
- data/lib/hamlit/filters/erb.rb +4 -6
- data/lib/hamlit/filters/escaped.rb +11 -9
- data/lib/hamlit/filters/javascript.rb +25 -8
- data/lib/hamlit/filters/less.rb +9 -7
- data/lib/hamlit/filters/markdown.rb +5 -6
- data/lib/hamlit/filters/plain.rb +11 -15
- data/lib/hamlit/filters/preserve.rb +15 -5
- data/lib/hamlit/filters/ruby.rb +3 -5
- data/lib/hamlit/filters/sass.rb +9 -7
- data/lib/hamlit/filters/scss.rb +9 -7
- data/lib/hamlit/filters/text_base.rb +24 -0
- data/lib/hamlit/filters/tilt_base.rb +47 -0
- data/lib/hamlit/hash_parser.rb +107 -0
- data/lib/hamlit/html.rb +9 -6
- data/lib/hamlit/identity.rb +12 -0
- data/lib/hamlit/object_ref.rb +29 -0
- data/lib/hamlit/parser.rb +25 -142
- data/lib/hamlit/parser/MIT-LICENSE +20 -0
- data/lib/hamlit/parser/README.md +28 -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 +801 -0
- data/lib/hamlit/parser/haml_util.rb +283 -0
- data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
- data/lib/hamlit/{helpers.rb → rails_helpers.rb} +2 -7
- data/lib/hamlit/rails_template.rb +30 -0
- data/lib/hamlit/railtie.rb +1 -12
- data/lib/hamlit/ruby_expression.rb +31 -0
- data/lib/hamlit/static_analyzer.rb +49 -0
- data/lib/hamlit/string_interpolation.rb +69 -0
- data/lib/hamlit/template.rb +8 -0
- data/lib/hamlit/utils.rb +9 -0
- data/lib/hamlit/version.rb +1 -1
- metadata +116 -324
- data/.rspec +0 -2
- data/benchmarks/benchmark.rb +0 -110
- data/benchmarks/view.slim +0 -17
- data/doc/README.md +0 -19
- data/doc/engine/indent.md +0 -48
- data/doc/engine/new_attribute.md +0 -77
- data/doc/engine/old_attributes.md +0 -198
- data/doc/engine/silent_script.md +0 -97
- data/doc/engine/tag.md +0 -48
- data/doc/engine/text.md +0 -64
- data/doc/faml/README.md +0 -16
- data/doc/faml/engine/indent.md +0 -48
- data/doc/faml/engine/old_attributes.md +0 -111
- data/doc/faml/engine/silent_script.md +0 -97
- data/doc/faml/engine/text.md +0 -59
- data/doc/faml/filters/erb.md +0 -24
- data/doc/faml/filters/javascript.md +0 -27
- data/doc/faml/filters/less.md +0 -57
- data/doc/faml/filters/plain.md +0 -25
- data/doc/filters/erb.md +0 -31
- data/doc/filters/javascript.md +0 -83
- data/doc/filters/less.md +0 -57
- data/doc/filters/markdown.md +0 -31
- data/doc/filters/plain.md +0 -25
- data/doc/haml/README.md +0 -15
- data/doc/haml/engine/new_attribute.md +0 -77
- data/doc/haml/engine/old_attributes.md +0 -142
- data/doc/haml/engine/tag.md +0 -48
- data/doc/haml/engine/text.md +0 -29
- data/doc/haml/filters/erb.md +0 -26
- data/doc/haml/filters/javascript.md +0 -76
- data/doc/haml/filters/markdown.md +0 -31
- data/lib/hamlit/attribute.rb +0 -78
- data/lib/hamlit/compilers/attributes.rb +0 -108
- data/lib/hamlit/compilers/comment.rb +0 -13
- data/lib/hamlit/compilers/doctype.rb +0 -39
- data/lib/hamlit/compilers/filter.rb +0 -53
- data/lib/hamlit/compilers/new_attribute.rb +0 -115
- data/lib/hamlit/compilers/old_attribute.rb +0 -241
- data/lib/hamlit/compilers/runtime_attribute.rb +0 -58
- data/lib/hamlit/compilers/script.rb +0 -31
- data/lib/hamlit/compilers/strip.rb +0 -19
- data/lib/hamlit/compilers/text.rb +0 -111
- data/lib/hamlit/concerns/attribute_builder.rb +0 -22
- data/lib/hamlit/concerns/balanceable.rb +0 -68
- data/lib/hamlit/concerns/deprecation.rb +0 -20
- data/lib/hamlit/concerns/error.rb +0 -31
- data/lib/hamlit/concerns/escapable.rb +0 -17
- data/lib/hamlit/concerns/included.rb +0 -28
- data/lib/hamlit/concerns/indentable.rb +0 -117
- data/lib/hamlit/concerns/lexable.rb +0 -32
- data/lib/hamlit/concerns/line_reader.rb +0 -62
- data/lib/hamlit/concerns/registerable.rb +0 -24
- data/lib/hamlit/concerns/string_interpolation.rb +0 -48
- data/lib/hamlit/concerns/whitespace.rb +0 -91
- data/lib/hamlit/filters/tilt.rb +0 -41
- data/lib/hamlit/parsers/attribute.rb +0 -71
- data/lib/hamlit/parsers/comment.rb +0 -30
- data/lib/hamlit/parsers/doctype.rb +0 -18
- data/lib/hamlit/parsers/filter.rb +0 -18
- data/lib/hamlit/parsers/multiline.rb +0 -58
- data/lib/hamlit/parsers/script.rb +0 -126
- data/lib/hamlit/parsers/tag.rb +0 -83
- data/lib/hamlit/parsers/text.rb +0 -28
- data/lib/hamlit/temple.rb +0 -9
- data/release +0 -6
- data/spec/Rakefile +0 -72
- data/spec/hamlit/engine/comment_spec.rb +0 -56
- data/spec/hamlit/engine/doctype_spec.rb +0 -19
- data/spec/hamlit/engine/error_spec.rb +0 -135
- data/spec/hamlit/engine/indent_spec.rb +0 -42
- data/spec/hamlit/engine/multiline_spec.rb +0 -44
- data/spec/hamlit/engine/new_attribute_spec.rb +0 -110
- data/spec/hamlit/engine/old_attributes_spec.rb +0 -404
- data/spec/hamlit/engine/script_spec.rb +0 -116
- data/spec/hamlit/engine/silent_script_spec.rb +0 -213
- data/spec/hamlit/engine/tag_spec.rb +0 -295
- data/spec/hamlit/engine/text_spec.rb +0 -239
- data/spec/hamlit/engine_spec.rb +0 -58
- data/spec/hamlit/filters/coffee_spec.rb +0 -60
- data/spec/hamlit/filters/css_spec.rb +0 -33
- data/spec/hamlit/filters/erb_spec.rb +0 -16
- data/spec/hamlit/filters/javascript_spec.rb +0 -82
- data/spec/hamlit/filters/less_spec.rb +0 -37
- data/spec/hamlit/filters/markdown_spec.rb +0 -30
- data/spec/hamlit/filters/plain_spec.rb +0 -15
- data/spec/hamlit/filters/ruby_spec.rb +0 -24
- data/spec/hamlit/filters/sass_spec.rb +0 -33
- data/spec/hamlit/filters/scss_spec.rb +0 -37
- data/spec/hamlit/haml_spec.rb +0 -910
- data/spec/rails/.gitignore +0 -18
- data/spec/rails/.rspec +0 -2
- data/spec/rails/Gemfile +0 -19
- data/spec/rails/README.rdoc +0 -28
- data/spec/rails/Rakefile +0 -6
- data/spec/rails/app/assets/images/.keep +0 -0
- data/spec/rails/app/assets/javascripts/application.js +0 -15
- data/spec/rails/app/assets/stylesheets/application.css +0 -15
- data/spec/rails/app/controllers/application_controller.rb +0 -8
- data/spec/rails/app/controllers/concerns/.keep +0 -0
- data/spec/rails/app/controllers/users_controller.rb +0 -23
- data/spec/rails/app/helpers/application_helper.rb +0 -2
- data/spec/rails/app/mailers/.keep +0 -0
- data/spec/rails/app/models/.keep +0 -0
- data/spec/rails/app/models/concerns/.keep +0 -0
- data/spec/rails/app/views/application/index.html.haml +0 -18
- data/spec/rails/app/views/layouts/application.html.haml +0 -12
- data/spec/rails/app/views/users/capture.html.haml +0 -5
- data/spec/rails/app/views/users/capture_haml.html.haml +0 -5
- data/spec/rails/app/views/users/form.html.haml +0 -2
- data/spec/rails/app/views/users/helpers.html.haml +0 -10
- data/spec/rails/app/views/users/index.html.haml +0 -9
- data/spec/rails/app/views/users/inline.html.haml +0 -6
- data/spec/rails/app/views/users/old_attributes.html.haml +0 -5
- data/spec/rails/app/views/users/safe_buffer.html.haml +0 -4
- data/spec/rails/app/views/users/whitespace.html.haml +0 -4
- data/spec/rails/bin/bundle +0 -3
- data/spec/rails/bin/rails +0 -8
- data/spec/rails/bin/rake +0 -8
- data/spec/rails/bin/setup +0 -29
- data/spec/rails/bin/spring +0 -15
- data/spec/rails/config.ru +0 -4
- data/spec/rails/config/application.rb +0 -34
- data/spec/rails/config/boot.rb +0 -3
- data/spec/rails/config/database.yml +0 -25
- data/spec/rails/config/environment.rb +0 -5
- data/spec/rails/config/environments/development.rb +0 -41
- data/spec/rails/config/environments/production.rb +0 -79
- data/spec/rails/config/environments/test.rb +0 -42
- data/spec/rails/config/initializers/assets.rb +0 -11
- data/spec/rails/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/rails/config/initializers/cookies_serializer.rb +0 -3
- data/spec/rails/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/rails/config/initializers/inflections.rb +0 -16
- data/spec/rails/config/initializers/mime_types.rb +0 -4
- data/spec/rails/config/initializers/session_store.rb +0 -3
- data/spec/rails/config/initializers/wrap_parameters.rb +0 -14
- data/spec/rails/config/locales/en.yml +0 -24
- data/spec/rails/config/routes.rb +0 -16
- data/spec/rails/config/secrets.yml +0 -22
- data/spec/rails/db/schema.rb +0 -16
- data/spec/rails/db/seeds.rb +0 -7
- data/spec/rails/lib/assets/.keep +0 -0
- data/spec/rails/lib/tasks/.keep +0 -0
- data/spec/rails/log/.keep +0 -0
- data/spec/rails/public/404.html +0 -67
- data/spec/rails/public/422.html +0 -67
- data/spec/rails/public/500.html +0 -66
- data/spec/rails/public/favicon.ico +0 -0
- data/spec/rails/public/robots.txt +0 -5
- data/spec/rails/spec/hamlit_spec.rb +0 -123
- data/spec/rails/spec/rails_helper.rb +0 -56
- data/spec/rails/spec/spec_helper.rb +0 -91
- data/spec/rails/vendor/assets/javascripts/.keep +0 -0
- data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
- data/spec/spec_helper.rb +0 -36
- data/spec/spec_helper/document_generator.rb +0 -93
- data/spec/spec_helper/render_helper.rb +0 -120
- data/spec/spec_helper/test_case.rb +0 -55
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'hamlit/parser/haml_parser'
|
2
|
+
require 'hamlit/parser/haml_compiler'
|
3
|
+
require 'hamlit/parser/haml_error'
|
4
|
+
|
5
|
+
module Hamlit
|
6
|
+
# This class encapsulates all of the configuration options that Haml
|
7
|
+
# understands. Please see the {file:REFERENCE.md#options Haml Reference} to
|
8
|
+
# learn how to set the options.
|
9
|
+
class HamlOptions
|
10
|
+
|
11
|
+
@defaults = {
|
12
|
+
:attr_wrapper => "'",
|
13
|
+
:autoclose => %w(area base basefont br col command embed frame
|
14
|
+
hr img input isindex keygen link menuitem meta
|
15
|
+
param source track wbr),
|
16
|
+
:encoding => "UTF-8",
|
17
|
+
:escape_attrs => true,
|
18
|
+
:escape_html => false,
|
19
|
+
:filename => '(haml)',
|
20
|
+
:format => :html5,
|
21
|
+
:hyphenate_data_attrs => true,
|
22
|
+
:line => 1,
|
23
|
+
:mime_type => 'text/html',
|
24
|
+
:preserve => %w(textarea pre code),
|
25
|
+
:remove_whitespace => false,
|
26
|
+
:suppress_eval => false,
|
27
|
+
:ugly => false,
|
28
|
+
:cdata => false,
|
29
|
+
:parser_class => ::Hamlit::HamlParser,
|
30
|
+
:compiler_class => ::Hamlit::HamlCompiler,
|
31
|
+
:trace => false
|
32
|
+
}
|
33
|
+
|
34
|
+
@valid_formats = [:html4, :html5, :xhtml]
|
35
|
+
|
36
|
+
@buffer_option_keys = [:autoclose, :preserve, :attr_wrapper, :ugly, :format,
|
37
|
+
:encoding, :escape_html, :escape_attrs, :hyphenate_data_attrs, :cdata]
|
38
|
+
|
39
|
+
# The default option values.
|
40
|
+
# @return Hash
|
41
|
+
def self.defaults
|
42
|
+
@defaults
|
43
|
+
end
|
44
|
+
|
45
|
+
# An array of valid values for the `:format` option.
|
46
|
+
# @return Array
|
47
|
+
def self.valid_formats
|
48
|
+
@valid_formats
|
49
|
+
end
|
50
|
+
|
51
|
+
# An array of keys that will be used to provide a hash of options to
|
52
|
+
# {Haml::Buffer}.
|
53
|
+
# @return Hash
|
54
|
+
def self.buffer_option_keys
|
55
|
+
@buffer_option_keys
|
56
|
+
end
|
57
|
+
|
58
|
+
# The character that should wrap element attributes. This defaults to `'`
|
59
|
+
# (an apostrophe). Characters of this type within the attributes will be
|
60
|
+
# escaped (e.g. by replacing them with `'`) if the character is an
|
61
|
+
# apostrophe or a quotation mark.
|
62
|
+
attr_reader :attr_wrapper
|
63
|
+
|
64
|
+
# A list of tag names that should be automatically self-closed if they have
|
65
|
+
# no content. This can also contain regular expressions that match tag names
|
66
|
+
# (or any object which responds to `#===`). Defaults to `['meta', 'img',
|
67
|
+
# 'link', 'br', 'hr', 'input', 'area', 'param', 'col', 'base']`.
|
68
|
+
attr_accessor :autoclose
|
69
|
+
|
70
|
+
# The encoding to use for the HTML output.
|
71
|
+
# This can be a string or an `Encoding` Object. Note that Haml **does not**
|
72
|
+
# automatically re-encode Ruby values; any strings coming from outside the
|
73
|
+
# application should be converted before being passed into the Haml
|
74
|
+
# template. Defaults to `Encoding.default_internal`; if that's not set,
|
75
|
+
# defaults to the encoding of the Haml template; if that's `US-ASCII`,
|
76
|
+
# defaults to `"UTF-8"`.
|
77
|
+
attr_reader :encoding
|
78
|
+
|
79
|
+
# Sets whether or not to escape HTML-sensitive characters in attributes. If
|
80
|
+
# this is true, all HTML-sensitive characters in attributes are escaped. If
|
81
|
+
# it's set to false, no HTML-sensitive characters in attributes are escaped.
|
82
|
+
# If it's set to `:once`, existing HTML escape sequences are preserved, but
|
83
|
+
# other HTML-sensitive characters are escaped.
|
84
|
+
#
|
85
|
+
# Defaults to `true`.
|
86
|
+
attr_accessor :escape_attrs
|
87
|
+
|
88
|
+
# Sets whether or not to escape HTML-sensitive characters in script. If this
|
89
|
+
# is true, `=` behaves like {file:REFERENCE.md#escaping_html `&=`};
|
90
|
+
# otherwise, it behaves like {file:REFERENCE.md#unescaping_html `!=`}. Note
|
91
|
+
# that if this is set, `!=` should be used for yielding to subtemplates and
|
92
|
+
# rendering partials. See also {file:REFERENCE.md#escaping_html Escaping HTML} and
|
93
|
+
# {file:REFERENCE.md#unescaping_html Unescaping HTML}.
|
94
|
+
#
|
95
|
+
# Defaults to false.
|
96
|
+
attr_accessor :escape_html
|
97
|
+
|
98
|
+
# The name of the Haml file being parsed.
|
99
|
+
# This is only used as information when exceptions are raised. This is
|
100
|
+
# automatically assigned when working through ActionView, so it's really
|
101
|
+
# only useful for the user to assign when dealing with Haml programatically.
|
102
|
+
attr_accessor :filename
|
103
|
+
|
104
|
+
# If set to `true`, Haml will convert underscores to hyphens in all
|
105
|
+
# {file:REFERENCE.md#html5_custom_data_attributes Custom Data Attributes} As
|
106
|
+
# of Haml 4.0, this defaults to `true`.
|
107
|
+
attr_accessor :hyphenate_data_attrs
|
108
|
+
|
109
|
+
# The line offset of the Haml template being parsed. This is useful for
|
110
|
+
# inline templates, similar to the last argument to `Kernel#eval`.
|
111
|
+
attr_accessor :line
|
112
|
+
|
113
|
+
# Determines the output format. The default is `:html5`. The other options
|
114
|
+
# are `:html4` and `:xhtml`. If the output is set to XHTML, then Haml
|
115
|
+
# automatically generates self-closing tags and wraps the output of the
|
116
|
+
# Javascript and CSS-like filters inside CDATA. When the output is set to
|
117
|
+
# `:html5` or `:html4`, XML prologs are ignored. In all cases, an appropriate
|
118
|
+
# doctype is generated from `!!!`.
|
119
|
+
#
|
120
|
+
# If the mime_type of the template being rendered is `text/xml` then a
|
121
|
+
# format of `:xhtml` will be used even if the global output format is set to
|
122
|
+
# `:html4` or `:html5`.
|
123
|
+
attr :format
|
124
|
+
|
125
|
+
# The mime type that the rendered document will be served with. If this is
|
126
|
+
# set to `text/xml` then the format will be overridden to `:xhtml` even if
|
127
|
+
# it has set to `:html4` or `:html5`.
|
128
|
+
attr_accessor :mime_type
|
129
|
+
|
130
|
+
# A list of tag names that should automatically have their newlines
|
131
|
+
# preserved using the {Haml::Helpers#preserve} helper. This means that any
|
132
|
+
# content given on the same line as the tag will be preserved. For example,
|
133
|
+
# `%textarea= "Foo\nBar"` compiles to `<textarea>Foo
Bar</textarea>`.
|
134
|
+
# Defaults to `['textarea', 'pre']`. See also
|
135
|
+
# {file:REFERENCE.md#whitespace_preservation Whitespace Preservation}.
|
136
|
+
attr_accessor :preserve
|
137
|
+
|
138
|
+
# If set to `true`, all tags are treated as if both
|
139
|
+
# {file:REFERENCE.md#whitespace_removal__and_ whitespace removal} options
|
140
|
+
# were present. Use with caution as this may cause whitespace-related
|
141
|
+
# formatting errors.
|
142
|
+
#
|
143
|
+
# Defaults to `false`.
|
144
|
+
attr_reader :remove_whitespace
|
145
|
+
|
146
|
+
# Whether or not attribute hashes and Ruby scripts designated by `=` or `~`
|
147
|
+
# should be evaluated. If this is `true`, said scripts are rendered as empty
|
148
|
+
# strings.
|
149
|
+
#
|
150
|
+
# Defaults to `false`.
|
151
|
+
attr_accessor :suppress_eval
|
152
|
+
|
153
|
+
# If set to `true`, Haml makes no attempt to properly indent or format the
|
154
|
+
# HTML output. This significantly improves rendering performance but makes
|
155
|
+
# viewing the source unpleasant.
|
156
|
+
#
|
157
|
+
# Defaults to `true` in Rails production mode, and `false` everywhere else.
|
158
|
+
attr_accessor :ugly
|
159
|
+
|
160
|
+
# Whether to include CDATA sections around javascript and css blocks when
|
161
|
+
# using the `:javascript` or `:css` filters.
|
162
|
+
#
|
163
|
+
# This option also affects the `:sass`, `:scss`, `:less` and `:coffeescript`
|
164
|
+
# filters.
|
165
|
+
#
|
166
|
+
# Defaults to `false` for html, `true` for xhtml. Cannot be changed when using
|
167
|
+
# xhtml.
|
168
|
+
attr_accessor :cdata
|
169
|
+
|
170
|
+
# The parser class to use. Defaults to Haml::Parser.
|
171
|
+
attr_accessor :parser_class
|
172
|
+
|
173
|
+
# The compiler class to use. Defaults to Haml::Compiler.
|
174
|
+
attr_accessor :compiler_class
|
175
|
+
|
176
|
+
# Enable template tracing. If true, it will add a 'data-trace' attribute to
|
177
|
+
# each tag generated by Haml. The value of the attribute will be the
|
178
|
+
# source template name and the line number from which the tag was generated,
|
179
|
+
# separated by a colon. On Rails applications, the path given will be a
|
180
|
+
# relative path as from the views directory. On non-Rails applications,
|
181
|
+
# the path will be the full path.
|
182
|
+
attr_accessor :trace
|
183
|
+
|
184
|
+
def initialize(values = {}, &block)
|
185
|
+
defaults.each {|k, v| instance_variable_set :"@#{k}", v}
|
186
|
+
values.each {|k, v| send("#{k}=", v) if defaults.has_key?(k) && !v.nil?}
|
187
|
+
yield if block_given?
|
188
|
+
end
|
189
|
+
|
190
|
+
# Retrieve an option value.
|
191
|
+
# @param key The value to retrieve.
|
192
|
+
def [](key)
|
193
|
+
send key
|
194
|
+
end
|
195
|
+
|
196
|
+
# Set an option value.
|
197
|
+
# @param key The key to set.
|
198
|
+
# @param value The value to set for the key.
|
199
|
+
def []=(key, value)
|
200
|
+
send "#{key}=", value
|
201
|
+
end
|
202
|
+
|
203
|
+
[:escape_attrs, :hyphenate_data_attrs, :remove_whitespace, :suppress_eval,
|
204
|
+
:ugly].each do |method|
|
205
|
+
class_eval(<<-END)
|
206
|
+
def #{method}?
|
207
|
+
!! @#{method}
|
208
|
+
end
|
209
|
+
END
|
210
|
+
end
|
211
|
+
|
212
|
+
# @return [Boolean] Whether or not the format is XHTML.
|
213
|
+
def xhtml?
|
214
|
+
not html?
|
215
|
+
end
|
216
|
+
|
217
|
+
# @return [Boolean] Whether or not the format is any flavor of HTML.
|
218
|
+
def html?
|
219
|
+
html4? or html5?
|
220
|
+
end
|
221
|
+
|
222
|
+
# @return [Boolean] Whether or not the format is HTML4.
|
223
|
+
def html4?
|
224
|
+
format == :html4
|
225
|
+
end
|
226
|
+
|
227
|
+
# @return [Boolean] Whether or not the format is HTML5.
|
228
|
+
def html5?
|
229
|
+
format == :html5
|
230
|
+
end
|
231
|
+
|
232
|
+
def attr_wrapper=(value)
|
233
|
+
@attr_wrapper = value || self.class.defaults[:attr_wrapper]
|
234
|
+
end
|
235
|
+
|
236
|
+
# Undef :format to suppress warning. It's defined above with the `:attr`
|
237
|
+
# macro in order to make it appear in Yard's list of instance attributes.
|
238
|
+
undef :format
|
239
|
+
def format
|
240
|
+
mime_type == "text/xml" ? :xhtml : @format
|
241
|
+
end
|
242
|
+
|
243
|
+
def format=(value)
|
244
|
+
unless self.class.valid_formats.include?(value)
|
245
|
+
raise ::Hamlit::HamlError, "Invalid output format #{value.inspect}"
|
246
|
+
end
|
247
|
+
@format = value
|
248
|
+
end
|
249
|
+
|
250
|
+
undef :cdata
|
251
|
+
def cdata
|
252
|
+
xhtml? || @cdata
|
253
|
+
end
|
254
|
+
|
255
|
+
def remove_whitespace=(value)
|
256
|
+
@ugly = true if value
|
257
|
+
@remove_whitespace = value
|
258
|
+
end
|
259
|
+
|
260
|
+
def encoding=(value)
|
261
|
+
return unless value
|
262
|
+
@encoding = value.is_a?(Encoding) ? value.name : value.to_s
|
263
|
+
@encoding = "UTF-8" if @encoding.upcase == "US-ASCII"
|
264
|
+
end
|
265
|
+
|
266
|
+
# Returns a subset of options: those that {Haml::Buffer} cares about.
|
267
|
+
# All of the values here are such that when `#inspect` is called on the hash,
|
268
|
+
# it can be `Kernel#eval`ed to get the same result back.
|
269
|
+
#
|
270
|
+
# See {file:REFERENCE.md#options the Haml options documentation}.
|
271
|
+
#
|
272
|
+
# @return [{Symbol => Object}] The options hash
|
273
|
+
def for_buffer
|
274
|
+
self.class.buffer_option_keys.inject({}) do |hash, key|
|
275
|
+
hash[key] = send(key)
|
276
|
+
hash
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
281
|
+
|
282
|
+
def defaults
|
283
|
+
self.class.defaults
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,801 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'hamlit/parser/haml_util'
|
3
|
+
require 'hamlit/parser/haml_buffer'
|
4
|
+
require 'hamlit/parser/haml_error'
|
5
|
+
|
6
|
+
module Hamlit
|
7
|
+
class HamlParser
|
8
|
+
include ::Hamlit::HamlUtil
|
9
|
+
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
# Designates an XHTML/XML element.
|
13
|
+
ELEMENT = ?%
|
14
|
+
|
15
|
+
# Designates a `<div>` element with the given class.
|
16
|
+
DIV_CLASS = ?.
|
17
|
+
|
18
|
+
# Designates a `<div>` element with the given id.
|
19
|
+
DIV_ID = ?#
|
20
|
+
|
21
|
+
# Designates an XHTML/XML comment.
|
22
|
+
COMMENT = ?/
|
23
|
+
|
24
|
+
# Designates an XHTML doctype or script that is never HTML-escaped.
|
25
|
+
DOCTYPE = ?!
|
26
|
+
|
27
|
+
# Designates script, the result of which is output.
|
28
|
+
SCRIPT = ?=
|
29
|
+
|
30
|
+
# Designates script that is always HTML-escaped.
|
31
|
+
SANITIZE = ?&
|
32
|
+
|
33
|
+
# Designates script, the result of which is flattened and output.
|
34
|
+
FLAT_SCRIPT = ?~
|
35
|
+
|
36
|
+
# Designates script which is run but not output.
|
37
|
+
SILENT_SCRIPT = ?-
|
38
|
+
|
39
|
+
# When following SILENT_SCRIPT, designates a comment that is not output.
|
40
|
+
SILENT_COMMENT = ?#
|
41
|
+
|
42
|
+
# Designates a non-parsed line.
|
43
|
+
ESCAPE = ?\\
|
44
|
+
|
45
|
+
# Designates a block of filtered text.
|
46
|
+
FILTER = ?:
|
47
|
+
|
48
|
+
# Designates a non-parsed line. Not actually a character.
|
49
|
+
PLAIN_TEXT = -1
|
50
|
+
|
51
|
+
# Keeps track of the ASCII values of the characters that begin a
|
52
|
+
# specially-interpreted line.
|
53
|
+
SPECIAL_CHARACTERS = [
|
54
|
+
ELEMENT,
|
55
|
+
DIV_CLASS,
|
56
|
+
DIV_ID,
|
57
|
+
COMMENT,
|
58
|
+
DOCTYPE,
|
59
|
+
SCRIPT,
|
60
|
+
SANITIZE,
|
61
|
+
FLAT_SCRIPT,
|
62
|
+
SILENT_SCRIPT,
|
63
|
+
ESCAPE,
|
64
|
+
FILTER
|
65
|
+
]
|
66
|
+
|
67
|
+
# The value of the character that designates that a line is part
|
68
|
+
# of a multiline string.
|
69
|
+
MULTILINE_CHAR_VALUE = ?|
|
70
|
+
|
71
|
+
# Regex to check for blocks with spaces around arguments. Not to be confused
|
72
|
+
# with multiline script.
|
73
|
+
# For example:
|
74
|
+
# foo.each do | bar |
|
75
|
+
# = bar
|
76
|
+
#
|
77
|
+
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
78
|
+
|
79
|
+
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
80
|
+
START_BLOCK_KEYWORDS = %w[if begin case unless]
|
81
|
+
# Try to parse assignments to block starters as best as possible
|
82
|
+
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
83
|
+
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
84
|
+
|
85
|
+
# The Regex that matches a Doctype command.
|
86
|
+
DOCTYPE_REGEX = /(\d(?:\.\d)?)?\s*([a-z]*)\s*([^ ]+)?/i
|
87
|
+
|
88
|
+
# The Regex that matches a literal string or symbol value
|
89
|
+
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?!\\|\#\{|\#@|\#\$|\2).|\\.)*\2/
|
90
|
+
|
91
|
+
ID_KEY = 'id'.freeze
|
92
|
+
CLASS_KEY = 'class'.freeze
|
93
|
+
|
94
|
+
def initialize(template, options)
|
95
|
+
@options = options
|
96
|
+
# Record the indent levels of "if" statements to validate the subsequent
|
97
|
+
# elsif and else statements are indented at the appropriate level.
|
98
|
+
@script_level_stack = []
|
99
|
+
@template_index = 0
|
100
|
+
@template_tabs = 0
|
101
|
+
|
102
|
+
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
103
|
+
# discard the last match which is always blank
|
104
|
+
match.pop
|
105
|
+
@template = match.each_with_index.map do |(full, whitespace, text), index|
|
106
|
+
Line.new(whitespace, text.rstrip, full, index, self, false)
|
107
|
+
end
|
108
|
+
# Append special end-of-document marker
|
109
|
+
@template << Line.new(nil, '-#', '-#', @template.size, self, true)
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse
|
113
|
+
@root = @parent = ParseNode.new(:root)
|
114
|
+
@flat = false
|
115
|
+
@filter_buffer = nil
|
116
|
+
@indentation = nil
|
117
|
+
@line = next_line
|
118
|
+
|
119
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:indenting_at_start), @line.index) if @line.tabs != 0
|
120
|
+
|
121
|
+
loop do
|
122
|
+
next_line
|
123
|
+
|
124
|
+
process_indent(@line) unless @line.text.empty?
|
125
|
+
|
126
|
+
if flat?
|
127
|
+
text = @line.full.dup
|
128
|
+
text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
|
129
|
+
@filter_buffer << "#{text}\n"
|
130
|
+
@line = @next_line
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
134
|
+
@tab_up = nil
|
135
|
+
process_line(@line) unless @line.text.empty?
|
136
|
+
if block_opened? || @tab_up
|
137
|
+
@template_tabs += 1
|
138
|
+
@parent = @parent.children.last
|
139
|
+
end
|
140
|
+
|
141
|
+
if !flat? && @next_line.tabs - @line.tabs > 1
|
142
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
|
143
|
+
end
|
144
|
+
|
145
|
+
@line = @next_line
|
146
|
+
end
|
147
|
+
# Close all the open tags
|
148
|
+
close until @parent.type == :root
|
149
|
+
@root
|
150
|
+
rescue ::Hamlit::HamlError => e
|
151
|
+
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
152
|
+
raise
|
153
|
+
end
|
154
|
+
|
155
|
+
def compute_tabs(line)
|
156
|
+
return 0 if line.text.empty? || !line.whitespace
|
157
|
+
|
158
|
+
if @indentation.nil?
|
159
|
+
@indentation = line.whitespace
|
160
|
+
|
161
|
+
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
162
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:cant_use_tabs_and_spaces), line.index)
|
163
|
+
end
|
164
|
+
|
165
|
+
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
166
|
+
return 1
|
167
|
+
end
|
168
|
+
|
169
|
+
tabs = line.whitespace.length / @indentation.length
|
170
|
+
return tabs if line.whitespace == @indentation * tabs
|
171
|
+
return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
|
172
|
+
|
173
|
+
message = ::Hamlit::HamlError.message(:inconsistent_indentation,
|
174
|
+
human_indentation(line.whitespace),
|
175
|
+
human_indentation(@indentation)
|
176
|
+
)
|
177
|
+
raise ::Hamlit::HamlSyntaxError.new(message, line.index)
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# @private
|
183
|
+
class Line < Struct.new(:whitespace, :text, :full, :index, :parser, :eod)
|
184
|
+
alias_method :eod?, :eod
|
185
|
+
|
186
|
+
# @private
|
187
|
+
def tabs
|
188
|
+
@tabs ||= parser.compute_tabs(self)
|
189
|
+
end
|
190
|
+
|
191
|
+
def strip!(from)
|
192
|
+
self.text = text[from..-1]
|
193
|
+
self.text.lstrip!
|
194
|
+
self
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# @private
|
199
|
+
class ParseNode < Struct.new(:type, :line, :value, :parent, :children)
|
200
|
+
def initialize(*args)
|
201
|
+
super
|
202
|
+
self.children ||= []
|
203
|
+
end
|
204
|
+
|
205
|
+
def inspect
|
206
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Processes and deals with lowering indentation.
|
211
|
+
def process_indent(line)
|
212
|
+
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
213
|
+
|
214
|
+
to_close = @template_tabs - line.tabs
|
215
|
+
to_close.times {|i| close unless to_close - 1 - i == 0 && continuation_script?(line.text)}
|
216
|
+
end
|
217
|
+
|
218
|
+
def continuation_script?(text)
|
219
|
+
text[0] == SILENT_SCRIPT && mid_block_keyword?(text)
|
220
|
+
end
|
221
|
+
|
222
|
+
def mid_block_keyword?(text)
|
223
|
+
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
224
|
+
end
|
225
|
+
|
226
|
+
# Processes a single line of Haml.
|
227
|
+
#
|
228
|
+
# This method doesn't return anything; it simply processes the line and
|
229
|
+
# adds the appropriate code to `@precompiled`.
|
230
|
+
def process_line(line)
|
231
|
+
case line.text[0]
|
232
|
+
when DIV_CLASS; push div(line)
|
233
|
+
when DIV_ID
|
234
|
+
return push plain(line) if %w[{ @ $].include?(line.text[1])
|
235
|
+
push div(line)
|
236
|
+
when ELEMENT; push tag(line)
|
237
|
+
when COMMENT; push comment(line.text[1..-1].lstrip)
|
238
|
+
when SANITIZE
|
239
|
+
return push plain(line.strip!(3), :escape_html) if line.text[1, 2] == '=='
|
240
|
+
return push script(line.strip!(2), :escape_html) if line.text[1] == SCRIPT
|
241
|
+
return push flat_script(line.strip!(2), :escape_html) if line.text[1] == FLAT_SCRIPT
|
242
|
+
return push plain(line.strip!(1), :escape_html) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
243
|
+
push plain(line)
|
244
|
+
when SCRIPT
|
245
|
+
return push plain(line.strip!(2)) if line.text[1] == SCRIPT
|
246
|
+
line.text = line.text[1..-1]
|
247
|
+
push script(line)
|
248
|
+
when FLAT_SCRIPT; push flat_script(line.strip!(1))
|
249
|
+
when SILENT_SCRIPT
|
250
|
+
return push haml_comment(line.text[2..-1]) if line.text[1] == SILENT_COMMENT
|
251
|
+
push silent_script(line)
|
252
|
+
when FILTER; push filter(line.text[1..-1].downcase)
|
253
|
+
when DOCTYPE
|
254
|
+
return push doctype(line.text) if line.text[0, 3] == '!!!'
|
255
|
+
return push plain(line.strip!(3), false) if line.text[1, 2] == '=='
|
256
|
+
return push script(line.strip!(2), false) if line.text[1] == SCRIPT
|
257
|
+
return push flat_script(line.strip!(2), false) if line.text[1] == FLAT_SCRIPT
|
258
|
+
return push plain(line.strip!(1), false) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
259
|
+
push plain(line)
|
260
|
+
when ESCAPE
|
261
|
+
line.text = line.text[1..-1]
|
262
|
+
push plain(line)
|
263
|
+
else; push plain(line)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def block_keyword(text)
|
268
|
+
return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
|
269
|
+
keyword[0] || keyword[1]
|
270
|
+
end
|
271
|
+
|
272
|
+
def push(node)
|
273
|
+
@parent.children << node
|
274
|
+
node.parent = @parent
|
275
|
+
end
|
276
|
+
|
277
|
+
def plain(line, escape_html = nil)
|
278
|
+
if block_opened?
|
279
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_plain), @next_line.index)
|
280
|
+
end
|
281
|
+
|
282
|
+
unless contains_interpolation?(line.text)
|
283
|
+
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
284
|
+
end
|
285
|
+
|
286
|
+
escape_html = @options.escape_html if escape_html.nil?
|
287
|
+
line.text = ::Hamlit::HamlUtil.unescape_interpolation(line.text)
|
288
|
+
script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
|
289
|
+
end
|
290
|
+
|
291
|
+
def script(line, escape_html = nil, preserve = false)
|
292
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, '=')) if line.text.empty?
|
293
|
+
line = handle_ruby_multiline(line)
|
294
|
+
escape_html = @options.escape_html if escape_html.nil?
|
295
|
+
|
296
|
+
keyword = block_keyword(line.text)
|
297
|
+
check_push_script_stack(keyword)
|
298
|
+
|
299
|
+
ParseNode.new(:script, line.index + 1, :text => line.text, :escape_html => escape_html,
|
300
|
+
:preserve => preserve, :keyword => keyword)
|
301
|
+
end
|
302
|
+
|
303
|
+
def flat_script(line, escape_html = nil)
|
304
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, '~')) if line.text.empty?
|
305
|
+
script(line, escape_html, :preserve)
|
306
|
+
end
|
307
|
+
|
308
|
+
def silent_script(line)
|
309
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
|
310
|
+
|
311
|
+
line = handle_ruby_multiline(line)
|
312
|
+
keyword = block_keyword(line.text)
|
313
|
+
|
314
|
+
check_push_script_stack(keyword)
|
315
|
+
|
316
|
+
if ["else", "elsif", "when"].include?(keyword)
|
317
|
+
if @script_level_stack.empty?
|
318
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:missing_if, keyword), @line.index)
|
319
|
+
end
|
320
|
+
|
321
|
+
if keyword == 'when' and !@script_level_stack.last[2]
|
322
|
+
if @script_level_stack.last[1] + 1 == @line.tabs
|
323
|
+
@script_level_stack.last[1] += 1
|
324
|
+
end
|
325
|
+
@script_level_stack.last[2] = true
|
326
|
+
end
|
327
|
+
|
328
|
+
if @script_level_stack.last[1] != @line.tabs
|
329
|
+
message = ::Hamlit::HamlError.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
|
330
|
+
raise ::Hamlit::HamlSyntaxError.new(message, @line.index)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
ParseNode.new(:silent_script, @line.index + 1,
|
335
|
+
:text => line.text[1..-1], :keyword => keyword)
|
336
|
+
end
|
337
|
+
|
338
|
+
def check_push_script_stack(keyword)
|
339
|
+
if ["if", "case", "unless"].include?(keyword)
|
340
|
+
# @script_level_stack contents are arrays of form
|
341
|
+
# [:keyword, stack_level, other_info]
|
342
|
+
@script_level_stack.push([keyword.to_sym, @line.tabs])
|
343
|
+
@script_level_stack.last << false if keyword == 'case'
|
344
|
+
@tab_up = true
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def haml_comment(text)
|
349
|
+
if filter_opened?
|
350
|
+
@flat = true
|
351
|
+
@filter_buffer = String.new
|
352
|
+
@filter_buffer << "#{text}\n" unless text.empty?
|
353
|
+
text = @filter_buffer
|
354
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
355
|
+
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
356
|
+
end
|
357
|
+
|
358
|
+
ParseNode.new(:haml_comment, @line.index + 1, :text => text)
|
359
|
+
end
|
360
|
+
|
361
|
+
def tag(line)
|
362
|
+
tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
363
|
+
nuke_inner_whitespace, action, value, last_line = parse_tag(line.text)
|
364
|
+
|
365
|
+
preserve_tag = @options.preserve.include?(tag_name)
|
366
|
+
nuke_inner_whitespace ||= preserve_tag
|
367
|
+
preserve_tag = false if @options.ugly
|
368
|
+
escape_html = (action == '&' || (action != '!' && @options.escape_html))
|
369
|
+
|
370
|
+
case action
|
371
|
+
when '/'; self_closing = true
|
372
|
+
when '~'; parse = preserve_script = true
|
373
|
+
when '='
|
374
|
+
parse = true
|
375
|
+
if value[0] == ?=
|
376
|
+
value = ::Hamlit::HamlUtil.unescape_interpolation(value[1..-1].strip)
|
377
|
+
escape_interpolation = true if escape_html
|
378
|
+
escape_html = false
|
379
|
+
end
|
380
|
+
when '&', '!'
|
381
|
+
if value[0] == ?= || value[0] == ?~
|
382
|
+
parse = true
|
383
|
+
preserve_script = (value[0] == ?~)
|
384
|
+
if value[1] == ?=
|
385
|
+
value = ::Hamlit::HamlUtil.unescape_interpolation(value[2..-1].strip)
|
386
|
+
escape_interpolation = true if escape_html
|
387
|
+
escape_html = false
|
388
|
+
else
|
389
|
+
value = value[1..-1].strip
|
390
|
+
end
|
391
|
+
elsif contains_interpolation?(value)
|
392
|
+
value = ::Hamlit::HamlUtil.unescape_interpolation(value)
|
393
|
+
escape_interpolation = true if escape_html
|
394
|
+
parse = true
|
395
|
+
escape_html = false
|
396
|
+
end
|
397
|
+
else
|
398
|
+
if contains_interpolation?(value)
|
399
|
+
value = ::Hamlit::HamlUtil.unescape_interpolation(value)
|
400
|
+
escape_interpolation = true if escape_html
|
401
|
+
parse = true
|
402
|
+
escape_html = false
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
attributes = ::Hamlit::HamlParser.parse_class_and_id(attributes)
|
407
|
+
attributes_list = []
|
408
|
+
|
409
|
+
if attributes_hashes[:new]
|
410
|
+
static_attributes, attributes_hash = attributes_hashes[:new]
|
411
|
+
::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
|
412
|
+
attributes_list << attributes_hash
|
413
|
+
end
|
414
|
+
|
415
|
+
if attributes_hashes[:old]
|
416
|
+
static_attributes = parse_static_hash(attributes_hashes[:old])
|
417
|
+
::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
|
418
|
+
attributes_list << attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
419
|
+
end
|
420
|
+
|
421
|
+
attributes_list.compact!
|
422
|
+
|
423
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
424
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
425
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
426
|
+
|
427
|
+
if block_opened? && !value.empty? && !is_ruby_multiline?(value)
|
428
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_line, tag_name), @next_line.index)
|
429
|
+
end
|
430
|
+
|
431
|
+
self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
|
432
|
+
value = nil if value.empty? && (block_opened? || self_closing)
|
433
|
+
line.text = value
|
434
|
+
line = handle_ruby_multiline(line) if parse
|
435
|
+
|
436
|
+
ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
|
437
|
+
:attributes_hashes => attributes_list, :self_closing => self_closing,
|
438
|
+
:nuke_inner_whitespace => nuke_inner_whitespace,
|
439
|
+
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
440
|
+
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
441
|
+
:preserve_script => preserve_script, :parse => parse, :value => line.text,
|
442
|
+
:escape_interpolation => escape_interpolation)
|
443
|
+
end
|
444
|
+
|
445
|
+
# Renders a line that creates an XHTML tag and has an implicit div because of
|
446
|
+
# `.` or `#`.
|
447
|
+
def div(line)
|
448
|
+
line.text = "%div#{line.text}"
|
449
|
+
tag(line)
|
450
|
+
end
|
451
|
+
|
452
|
+
# Renders an XHTML comment.
|
453
|
+
def comment(text)
|
454
|
+
if text[0..1] == '!['
|
455
|
+
revealed = true
|
456
|
+
text = text[1..-1]
|
457
|
+
else
|
458
|
+
revealed = false
|
459
|
+
end
|
460
|
+
|
461
|
+
conditional, text = balance(text, ?[, ?]) if text[0] == ?[
|
462
|
+
text.strip!
|
463
|
+
|
464
|
+
if contains_interpolation?(text)
|
465
|
+
parse = true
|
466
|
+
text = slow_unescape_interpolation(text)
|
467
|
+
else
|
468
|
+
parse = false
|
469
|
+
end
|
470
|
+
|
471
|
+
if block_opened? && !text.empty?
|
472
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_content), @next_line.index)
|
473
|
+
end
|
474
|
+
|
475
|
+
ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
|
476
|
+
end
|
477
|
+
|
478
|
+
# Renders an XHTML doctype or XML shebang.
|
479
|
+
def doctype(text)
|
480
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_header), @next_line.index) if block_opened?
|
481
|
+
version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
482
|
+
ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
|
483
|
+
end
|
484
|
+
|
485
|
+
def filter(name)
|
486
|
+
raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
|
487
|
+
|
488
|
+
if filter_opened?
|
489
|
+
@flat = true
|
490
|
+
@filter_buffer = String.new
|
491
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
492
|
+
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
493
|
+
end
|
494
|
+
|
495
|
+
ParseNode.new(:filter, @line.index + 1, :name => name, :text => @filter_buffer)
|
496
|
+
end
|
497
|
+
|
498
|
+
def close
|
499
|
+
node, @parent = @parent, @parent.parent
|
500
|
+
@template_tabs -= 1
|
501
|
+
send("close_#{node.type}", node) if respond_to?("close_#{node.type}", :include_private)
|
502
|
+
end
|
503
|
+
|
504
|
+
def close_filter(_)
|
505
|
+
close_flat_section
|
506
|
+
end
|
507
|
+
|
508
|
+
def close_haml_comment(_)
|
509
|
+
close_flat_section
|
510
|
+
end
|
511
|
+
|
512
|
+
def close_flat_section
|
513
|
+
@flat = false
|
514
|
+
@flat_spaces = nil
|
515
|
+
@filter_buffer = nil
|
516
|
+
end
|
517
|
+
|
518
|
+
def close_silent_script(node)
|
519
|
+
@script_level_stack.pop if ["if", "case", "unless"].include? node.value[:keyword]
|
520
|
+
|
521
|
+
# Post-process case statements to normalize the nesting of "when" clauses
|
522
|
+
return unless node.value[:keyword] == "case"
|
523
|
+
return unless first = node.children.first
|
524
|
+
return unless first.type == :silent_script && first.value[:keyword] == "when"
|
525
|
+
return if first.children.empty?
|
526
|
+
# If the case node has a "when" child with children, it's the
|
527
|
+
# only child. Then we want to put everything nested beneath it
|
528
|
+
# beneath the case itself (just like "if").
|
529
|
+
node.children = [first, *first.children]
|
530
|
+
first.children = []
|
531
|
+
end
|
532
|
+
|
533
|
+
alias :close_script :close_silent_script
|
534
|
+
|
535
|
+
# This is a class method so it can be accessed from {Haml::Helpers}.
|
536
|
+
#
|
537
|
+
# Iterates through the classes and ids supplied through `.`
|
538
|
+
# and `#` syntax, and returns a hash with them as attributes,
|
539
|
+
# that can then be merged with another attributes hash.
|
540
|
+
def self.parse_class_and_id(list)
|
541
|
+
attributes = {}
|
542
|
+
return attributes if list.empty?
|
543
|
+
|
544
|
+
list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
|
545
|
+
case type
|
546
|
+
when '.'
|
547
|
+
if attributes[CLASS_KEY]
|
548
|
+
attributes[CLASS_KEY] += " "
|
549
|
+
else
|
550
|
+
attributes[CLASS_KEY] = ""
|
551
|
+
end
|
552
|
+
attributes[CLASS_KEY] += property
|
553
|
+
when '#'; attributes[ID_KEY] = property
|
554
|
+
end
|
555
|
+
end
|
556
|
+
attributes
|
557
|
+
end
|
558
|
+
|
559
|
+
def parse_static_hash(text)
|
560
|
+
attributes = {}
|
561
|
+
return attributes if text.empty?
|
562
|
+
|
563
|
+
scanner = StringScanner.new(text)
|
564
|
+
scanner.scan(/\s+/)
|
565
|
+
until scanner.eos?
|
566
|
+
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
567
|
+
return unless scanner.scan(/\s*=>\s*/)
|
568
|
+
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
569
|
+
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
570
|
+
attributes[eval(key).to_s] = eval(value).to_s
|
571
|
+
end
|
572
|
+
attributes
|
573
|
+
end
|
574
|
+
|
575
|
+
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
576
|
+
def parse_tag(text)
|
577
|
+
match = text.scan(/%([-:\w]+)([-:\w.#]*)(.+)?/)[0]
|
578
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:invalid_tag, text)) unless match
|
579
|
+
|
580
|
+
tag_name, attributes, rest = match
|
581
|
+
|
582
|
+
if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
|
583
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_element))
|
584
|
+
end
|
585
|
+
|
586
|
+
new_attributes_hash = old_attributes_hash = last_line = nil
|
587
|
+
object_ref = :nil
|
588
|
+
attributes_hashes = {}
|
589
|
+
while rest && !rest.empty?
|
590
|
+
case rest[0]
|
591
|
+
when ?{
|
592
|
+
break if old_attributes_hash
|
593
|
+
old_attributes_hash, rest, last_line = parse_old_attributes(rest)
|
594
|
+
attributes_hashes[:old] = old_attributes_hash
|
595
|
+
when ?(
|
596
|
+
break if new_attributes_hash
|
597
|
+
new_attributes_hash, rest, last_line = parse_new_attributes(rest)
|
598
|
+
attributes_hashes[:new] = new_attributes_hash
|
599
|
+
when ?[
|
600
|
+
break unless object_ref == :nil
|
601
|
+
object_ref, rest = balance(rest, ?[, ?])
|
602
|
+
else; break
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
if rest && !rest.empty?
|
607
|
+
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
608
|
+
if nuke_whitespace
|
609
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
610
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
if @options.remove_whitespace
|
615
|
+
nuke_outer_whitespace = true
|
616
|
+
nuke_inner_whitespace = true
|
617
|
+
end
|
618
|
+
|
619
|
+
if value.nil?
|
620
|
+
value = ''
|
621
|
+
else
|
622
|
+
value.strip!
|
623
|
+
end
|
624
|
+
[tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
625
|
+
nuke_inner_whitespace, action, value, last_line || @line.index + 1]
|
626
|
+
end
|
627
|
+
|
628
|
+
def parse_old_attributes(text)
|
629
|
+
text = text.dup
|
630
|
+
last_line = @line.index + 1
|
631
|
+
|
632
|
+
begin
|
633
|
+
attributes_hash, rest = balance(text, ?{, ?})
|
634
|
+
rescue ::Hamlit::HamlSyntaxError => e
|
635
|
+
if text.strip[-1] == ?, && e.message == ::Hamlit::HamlError.message(:unbalanced_brackets)
|
636
|
+
text << "\n#{@next_line.text}"
|
637
|
+
last_line += 1
|
638
|
+
next_line
|
639
|
+
retry
|
640
|
+
end
|
641
|
+
|
642
|
+
raise e
|
643
|
+
end
|
644
|
+
|
645
|
+
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
646
|
+
return attributes_hash, rest, last_line
|
647
|
+
end
|
648
|
+
|
649
|
+
def parse_new_attributes(text)
|
650
|
+
scanner = StringScanner.new(text)
|
651
|
+
last_line = @line.index + 1
|
652
|
+
attributes = {}
|
653
|
+
|
654
|
+
scanner.scan(/\(\s*/)
|
655
|
+
loop do
|
656
|
+
name, value = parse_new_attribute(scanner)
|
657
|
+
break if name.nil?
|
658
|
+
|
659
|
+
if name == false
|
660
|
+
scanned = ::Hamlit::HamlUtil.balance(text, ?(, ?))
|
661
|
+
text = scanned ? scanned.first : text
|
662
|
+
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:invalid_attribute_list, text.inspect), last_line - 1)
|
663
|
+
end
|
664
|
+
attributes[name] = value
|
665
|
+
scanner.scan(/\s*/)
|
666
|
+
|
667
|
+
if scanner.eos?
|
668
|
+
text << " #{@next_line.text}"
|
669
|
+
last_line += 1
|
670
|
+
next_line
|
671
|
+
scanner.scan(/\s*/)
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
static_attributes = {}
|
676
|
+
dynamic_attributes = "{"
|
677
|
+
attributes.each do |name, (type, val)|
|
678
|
+
if type == :static
|
679
|
+
static_attributes[name] = val
|
680
|
+
else
|
681
|
+
dynamic_attributes << "#{inspect_obj(name)} => #{val},"
|
682
|
+
end
|
683
|
+
end
|
684
|
+
dynamic_attributes << "}"
|
685
|
+
dynamic_attributes = nil if dynamic_attributes == "{}"
|
686
|
+
|
687
|
+
return [static_attributes, dynamic_attributes], scanner.rest, last_line
|
688
|
+
end
|
689
|
+
|
690
|
+
def parse_new_attribute(scanner)
|
691
|
+
unless name = scanner.scan(/[-:\w]+/)
|
692
|
+
return if scanner.scan(/\)/)
|
693
|
+
return false
|
694
|
+
end
|
695
|
+
|
696
|
+
scanner.scan(/\s*/)
|
697
|
+
return name, [:static, true] unless scanner.scan(/=/) #/end
|
698
|
+
|
699
|
+
scanner.scan(/\s*/)
|
700
|
+
unless quote = scanner.scan(/["']/)
|
701
|
+
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
702
|
+
return name, [:dynamic, var]
|
703
|
+
end
|
704
|
+
|
705
|
+
re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
|
706
|
+
content = []
|
707
|
+
loop do
|
708
|
+
return false unless scanner.scan(re)
|
709
|
+
content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
|
710
|
+
break if scanner[2] == quote
|
711
|
+
content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
|
712
|
+
end
|
713
|
+
|
714
|
+
return name, [:static, content.first[1]] if content.size == 1
|
715
|
+
return name, [:dynamic,
|
716
|
+
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
717
|
+
end
|
718
|
+
|
719
|
+
def next_line
|
720
|
+
line = @template.shift || raise(StopIteration)
|
721
|
+
|
722
|
+
# `flat?' here is a little outdated,
|
723
|
+
# so we have to manually check if either the previous or current line
|
724
|
+
# closes the flat block, as well as whether a new block is opened.
|
725
|
+
line_defined = instance_variable_defined?(:@line)
|
726
|
+
@line.tabs if line_defined
|
727
|
+
unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
|
728
|
+
(line_defined && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
|
729
|
+
return next_line if line.text.empty?
|
730
|
+
|
731
|
+
handle_multiline(line)
|
732
|
+
end
|
733
|
+
|
734
|
+
@next_line = line
|
735
|
+
end
|
736
|
+
|
737
|
+
def closes_flat?(line)
|
738
|
+
line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
|
739
|
+
end
|
740
|
+
|
741
|
+
def handle_multiline(line)
|
742
|
+
return unless is_multiline?(line.text)
|
743
|
+
line.text.slice!(-1)
|
744
|
+
loop do
|
745
|
+
new_line = @template.first
|
746
|
+
break if new_line.eod?
|
747
|
+
next @template.shift if new_line.text.strip.empty?
|
748
|
+
break unless is_multiline?(new_line.text.strip)
|
749
|
+
line.text << new_line.text.strip[0...-1]
|
750
|
+
@template.shift
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
# Checks whether or not `line` is in a multiline sequence.
|
755
|
+
def is_multiline?(text)
|
756
|
+
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s && text !~ BLOCK_WITH_SPACES
|
757
|
+
end
|
758
|
+
|
759
|
+
def handle_ruby_multiline(line)
|
760
|
+
line.text.rstrip!
|
761
|
+
return line unless is_ruby_multiline?(line.text)
|
762
|
+
begin
|
763
|
+
# Use already fetched @next_line in the first loop. Otherwise, fetch next
|
764
|
+
new_line = new_line.nil? ? @next_line : @template.shift
|
765
|
+
break if new_line.eod?
|
766
|
+
next if new_line.text.empty?
|
767
|
+
line.text << " #{new_line.text.rstrip}"
|
768
|
+
end while is_ruby_multiline?(new_line.text)
|
769
|
+
next_line
|
770
|
+
line
|
771
|
+
end
|
772
|
+
|
773
|
+
# `text' is a Ruby multiline block if it:
|
774
|
+
# - ends with a comma
|
775
|
+
# - but not "?," which is a character literal
|
776
|
+
# (however, "x?," is a method call and not a literal)
|
777
|
+
# - and not "?\," which is a character literal
|
778
|
+
def is_ruby_multiline?(text)
|
779
|
+
text && text.length > 1 && text[-1] == ?, &&
|
780
|
+
!((text[-3, 2] =~ /\W\?/) || text[-3, 2] == "?\\")
|
781
|
+
end
|
782
|
+
|
783
|
+
def balance(*args)
|
784
|
+
::Hamlit::HamlUtil.balance(*args) or raise(::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:unbalanced_brackets)))
|
785
|
+
end
|
786
|
+
|
787
|
+
def block_opened?
|
788
|
+
@next_line.tabs > @line.tabs
|
789
|
+
end
|
790
|
+
|
791
|
+
# Same semantics as block_opened?, except that block_opened? uses Line#tabs,
|
792
|
+
# which doesn't interact well with filter lines
|
793
|
+
def filter_opened?
|
794
|
+
@next_line.full =~ (@indentation ? /^#{@indentation * (@template_tabs + 1)}/ : /^\s/)
|
795
|
+
end
|
796
|
+
|
797
|
+
def flat?
|
798
|
+
@flat
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|