haml_ejs 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +11 -0
- data/CONTRIBUTING +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +68 -0
- data/Rakefile +419 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/haml.rb +49 -0
- data/lib/haml/buffer.rb +297 -0
- data/lib/haml/compiler.rb +486 -0
- data/lib/haml/engine.rb +312 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +362 -0
- data/lib/haml/filters.rb +388 -0
- data/lib/haml/haml_ejs.rb +57 -0
- data/lib/haml/helpers.rb +608 -0
- data/lib/haml/helpers/action_view_extensions.rb +57 -0
- data/lib/haml/helpers/action_view_mods.rb +257 -0
- data/lib/haml/helpers/xss_mods.rb +165 -0
- data/lib/haml/html.rb +412 -0
- data/lib/haml/html/erb.rb +141 -0
- data/lib/haml/parser.rb +760 -0
- data/lib/haml/railtie.rb +19 -0
- data/lib/haml/root.rb +7 -0
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +99 -0
- data/lib/haml/template/options.rb +16 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +123 -0
- data/lib/haml/util.rb +842 -0
- data/lib/haml/version.rb +109 -0
- data/lib/sass.rb +8 -0
- data/lib/sass/plugin.rb +10 -0
- data/lib/sass/rails2_shim.rb +9 -0
- data/lib/sass/rails3_shim.rb +16 -0
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +91 -0
- data/test/gemfiles/Gemfile.rails-2.0.x +8 -0
- data/test/gemfiles/Gemfile.rails-2.0.x.lock +38 -0
- data/test/gemfiles/Gemfile.rails-2.1.x +8 -0
- data/test/gemfiles/Gemfile.rails-2.1.x.lock +38 -0
- data/test/gemfiles/Gemfile.rails-2.2.x +8 -0
- data/test/gemfiles/Gemfile.rails-2.2.x.lock +38 -0
- data/test/gemfiles/Gemfile.rails-2.3.x +8 -0
- data/test/gemfiles/Gemfile.rails-2.3.x.lock +40 -0
- data/test/gemfiles/Gemfile.rails-3.0.x +8 -0
- data/test/gemfiles/Gemfile.rails-3.0.x.lock +85 -0
- data/test/gemfiles/Gemfile.rails-3.1.x +8 -0
- data/test/gemfiles/Gemfile.rails-3.1.x.lock +98 -0
- data/test/gemfiles/Gemfile.rails-xss-2.3.x +9 -0
- data/test/gemfiles/Gemfile.rails-xss-2.3.x.lock +42 -0
- data/test/haml-ejs/haml-ejs_test.rb +22 -0
- data/test/haml-ejs/templates/conditional.input +3 -0
- data/test/haml-ejs/templates/conditional.output +3 -0
- data/test/haml-ejs/templates/conditional_and_interpolation.input +2 -0
- data/test/haml-ejs/templates/conditional_and_interpolation.output +3 -0
- data/test/haml-ejs/templates/conditional_else.input +5 -0
- data/test/haml-ejs/templates/conditional_else.output +5 -0
- data/test/haml-ejs/templates/conditional_else_elsif.input +7 -0
- data/test/haml-ejs/templates/conditional_else_elsif.output +7 -0
- data/test/haml-ejs/templates/conditional_elsif.input +5 -0
- data/test/haml-ejs/templates/conditional_elsif.output +5 -0
- data/test/haml-ejs/templates/conditional_unless.input +3 -0
- data/test/haml-ejs/templates/conditional_unless.output +3 -0
- data/test/haml-ejs/templates/conditional_unless_else.input +5 -0
- data/test/haml-ejs/templates/conditional_unless_else.output +5 -0
- data/test/haml-ejs/templates/conditional_unless_elsif_else.input +5 -0
- data/test/haml-ejs/templates/conditional_unless_elsif_else.output +5 -0
- data/test/haml-ejs/templates/interpolation.input +2 -0
- data/test/haml-ejs/templates/interpolation.output +1 -0
- data/test/haml-ejs/templates/iteration.input +2 -0
- data/test/haml-ejs/templates/iteration.output +3 -0
- data/test/haml-ejs/templates/suite.input +26 -0
- data/test/haml-ejs/templates/suite.output +27 -0
- data/test/haml/engine_test.rb +1925 -0
- data/test/haml/erb/_av_partial_1.erb +12 -0
- data/test/haml/erb/_av_partial_2.erb +8 -0
- data/test/haml/erb/action_view.erb +62 -0
- data/test/haml/erb/standard.erb +55 -0
- data/test/haml/helper_test.rb +454 -0
- data/test/haml/html2haml/erb_tests.rb +440 -0
- data/test/haml/html2haml_test.rb +336 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +12 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +70 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +70 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +162 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/spec/README.md +97 -0
- data/test/haml/spec/lua_haml_spec.lua +30 -0
- data/test/haml/spec/ruby_haml_test.rb +19 -0
- data/test/haml/spec/tests.json +534 -0
- data/test/haml/spec_test.rb +44 -0
- data/test/haml/template_test.rb +419 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +8 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +55 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +85 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +10 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +43 -0
- data/test/haml/templates/standard_ugly.haml +43 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/haml/util_test.rb +300 -0
- data/test/linked_rails.rb +42 -0
- data/test/test_helper.rb +75 -0
- data/vendor/sass/CONTRIBUTING +3 -0
- data/vendor/sass/MIT-LICENSE +20 -0
- data/vendor/sass/README.md +201 -0
- data/vendor/sass/Rakefile +339 -0
- data/vendor/sass/TODO +39 -0
- data/vendor/sass/VERSION +1 -0
- data/vendor/sass/VERSION_NAME +1 -0
- data/vendor/sass/bin/sass +8 -0
- data/vendor/sass/bin/sass-convert +7 -0
- data/vendor/sass/bin/scss +8 -0
- data/vendor/sass/doc-src/FAQ.md +35 -0
- data/vendor/sass/doc-src/INDENTED_SYNTAX.md +210 -0
- data/vendor/sass/doc-src/SASS_CHANGELOG.md +2214 -0
- data/vendor/sass/doc-src/SASS_REFERENCE.md +1957 -0
- data/vendor/sass/doc-src/SCSS_FOR_SASS_USERS.md +155 -0
- data/vendor/sass/ext/extconf.rb +10 -0
- data/vendor/sass/extra/update_watch.rb +13 -0
- data/vendor/sass/init.rb +18 -0
- data/vendor/sass/lib/sass.rb +72 -0
- data/vendor/sass/lib/sass/cache_stores.rb +15 -0
- data/vendor/sass/lib/sass/cache_stores/base.rb +84 -0
- data/vendor/sass/lib/sass/cache_stores/chain.rb +33 -0
- data/vendor/sass/lib/sass/cache_stores/filesystem.rb +58 -0
- data/vendor/sass/lib/sass/cache_stores/memory.rb +47 -0
- data/vendor/sass/lib/sass/cache_stores/null.rb +25 -0
- data/vendor/sass/lib/sass/callbacks.rb +66 -0
- data/vendor/sass/lib/sass/css.rb +294 -0
- data/vendor/sass/lib/sass/engine.rb +862 -0
- data/vendor/sass/lib/sass/environment.rb +155 -0
- data/vendor/sass/lib/sass/error.rb +201 -0
- data/vendor/sass/lib/sass/exec.rb +659 -0
- data/vendor/sass/lib/sass/importers.rb +22 -0
- data/vendor/sass/lib/sass/importers/base.rb +138 -0
- data/vendor/sass/lib/sass/importers/filesystem.rb +144 -0
- data/vendor/sass/lib/sass/less.rb +382 -0
- data/vendor/sass/lib/sass/plugin.rb +136 -0
- data/vendor/sass/lib/sass/plugin/compiler.rb +358 -0
- data/vendor/sass/lib/sass/plugin/configuration.rb +125 -0
- data/vendor/sass/lib/sass/plugin/generic.rb +15 -0
- data/vendor/sass/lib/sass/plugin/merb.rb +48 -0
- data/vendor/sass/lib/sass/plugin/rack.rb +60 -0
- data/vendor/sass/lib/sass/plugin/rails.rb +47 -0
- data/vendor/sass/lib/sass/plugin/staleness_checker.rb +173 -0
- data/vendor/sass/lib/sass/railtie.rb +9 -0
- data/vendor/sass/lib/sass/repl.rb +58 -0
- data/vendor/sass/lib/sass/root.rb +7 -0
- data/vendor/sass/lib/sass/script.rb +40 -0
- data/vendor/sass/lib/sass/script/bool.rb +18 -0
- data/vendor/sass/lib/sass/script/color.rb +480 -0
- data/vendor/sass/lib/sass/script/css_lexer.rb +29 -0
- data/vendor/sass/lib/sass/script/css_parser.rb +31 -0
- data/vendor/sass/lib/sass/script/funcall.rb +162 -0
- data/vendor/sass/lib/sass/script/functions.rb +1343 -0
- data/vendor/sass/lib/sass/script/interpolation.rb +70 -0
- data/vendor/sass/lib/sass/script/lexer.rb +334 -0
- data/vendor/sass/lib/sass/script/list.rb +76 -0
- data/vendor/sass/lib/sass/script/literal.rb +245 -0
- data/vendor/sass/lib/sass/script/node.rb +91 -0
- data/vendor/sass/lib/sass/script/number.rb +429 -0
- data/vendor/sass/lib/sass/script/operation.rb +91 -0
- data/vendor/sass/lib/sass/script/parser.rb +467 -0
- data/vendor/sass/lib/sass/script/string.rb +51 -0
- data/vendor/sass/lib/sass/script/string_interpolation.rb +94 -0
- data/vendor/sass/lib/sass/script/unary_operation.rb +57 -0
- data/vendor/sass/lib/sass/script/variable.rb +54 -0
- data/vendor/sass/lib/sass/scss.rb +17 -0
- data/vendor/sass/lib/sass/scss/css_parser.rb +46 -0
- data/vendor/sass/lib/sass/scss/parser.rb +920 -0
- data/vendor/sass/lib/sass/scss/rx.rb +127 -0
- data/vendor/sass/lib/sass/scss/sass_parser.rb +11 -0
- data/vendor/sass/lib/sass/scss/script_lexer.rb +15 -0
- data/vendor/sass/lib/sass/scss/script_parser.rb +25 -0
- data/vendor/sass/lib/sass/scss/static_parser.rb +40 -0
- data/vendor/sass/lib/sass/selector.rb +361 -0
- data/vendor/sass/lib/sass/selector/abstract_sequence.rb +62 -0
- data/vendor/sass/lib/sass/selector/comma_sequence.rb +81 -0
- data/vendor/sass/lib/sass/selector/sequence.rb +233 -0
- data/vendor/sass/lib/sass/selector/simple.rb +113 -0
- data/vendor/sass/lib/sass/selector/simple_sequence.rb +134 -0
- data/vendor/sass/lib/sass/shared.rb +78 -0
- data/vendor/sass/lib/sass/tree/charset_node.rb +22 -0
- data/vendor/sass/lib/sass/tree/comment_node.rb +77 -0
- data/vendor/sass/lib/sass/tree/debug_node.rb +18 -0
- data/vendor/sass/lib/sass/tree/directive_node.rb +23 -0
- data/vendor/sass/lib/sass/tree/each_node.rb +24 -0
- data/vendor/sass/lib/sass/tree/extend_node.rb +29 -0
- data/vendor/sass/lib/sass/tree/for_node.rb +36 -0
- data/vendor/sass/lib/sass/tree/function_node.rb +27 -0
- data/vendor/sass/lib/sass/tree/if_node.rb +65 -0
- data/vendor/sass/lib/sass/tree/import_node.rb +68 -0
- data/vendor/sass/lib/sass/tree/media_node.rb +32 -0
- data/vendor/sass/lib/sass/tree/mixin_def_node.rb +27 -0
- data/vendor/sass/lib/sass/tree/mixin_node.rb +32 -0
- data/vendor/sass/lib/sass/tree/node.rb +204 -0
- data/vendor/sass/lib/sass/tree/prop_node.rb +155 -0
- data/vendor/sass/lib/sass/tree/return_node.rb +18 -0
- data/vendor/sass/lib/sass/tree/root_node.rb +28 -0
- data/vendor/sass/lib/sass/tree/rule_node.rb +129 -0
- data/vendor/sass/lib/sass/tree/variable_node.rb +30 -0
- data/vendor/sass/lib/sass/tree/visitors/base.rb +75 -0
- data/vendor/sass/lib/sass/tree/visitors/check_nesting.rb +134 -0
- data/vendor/sass/lib/sass/tree/visitors/convert.rb +255 -0
- data/vendor/sass/lib/sass/tree/visitors/cssize.rb +175 -0
- data/vendor/sass/lib/sass/tree/visitors/perform.rb +301 -0
- data/vendor/sass/lib/sass/tree/visitors/to_css.rb +216 -0
- data/vendor/sass/lib/sass/tree/warn_node.rb +18 -0
- data/vendor/sass/lib/sass/tree/while_node.rb +18 -0
- data/vendor/sass/lib/sass/util.rb +669 -0
- data/vendor/sass/lib/sass/util/subset_map.rb +101 -0
- data/vendor/sass/lib/sass/version.rb +112 -0
- data/vendor/sass/rails/init.rb +1 -0
- data/vendor/sass/sass.gemspec +32 -0
- data/vendor/sass/test/sass/cache_test.rb +74 -0
- data/vendor/sass/test/sass/callbacks_test.rb +61 -0
- data/vendor/sass/test/sass/conversion_test.rb +1203 -0
- data/vendor/sass/test/sass/css2sass_test.rb +364 -0
- data/vendor/sass/test/sass/data/hsl-rgb.txt +319 -0
- data/vendor/sass/test/sass/engine_test.rb +2469 -0
- data/vendor/sass/test/sass/extend_test.rb +1348 -0
- data/vendor/sass/test/sass/functions_test.rb +1025 -0
- data/vendor/sass/test/sass/importer_test.rb +82 -0
- data/vendor/sass/test/sass/less_conversion_test.rb +653 -0
- data/vendor/sass/test/sass/mock_importer.rb +49 -0
- data/vendor/sass/test/sass/more_results/more1.css +9 -0
- data/vendor/sass/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/vendor/sass/test/sass/more_results/more_import.css +29 -0
- data/vendor/sass/test/sass/more_templates/_more_partial.sass +2 -0
- data/vendor/sass/test/sass/more_templates/more1.sass +23 -0
- data/vendor/sass/test/sass/more_templates/more_import.sass +11 -0
- data/vendor/sass/test/sass/plugin_test.rb +469 -0
- data/vendor/sass/test/sass/results/alt.css +4 -0
- data/vendor/sass/test/sass/results/basic.css +9 -0
- data/vendor/sass/test/sass/results/compact.css +5 -0
- data/vendor/sass/test/sass/results/complex.css +86 -0
- data/vendor/sass/test/sass/results/compressed.css +1 -0
- data/vendor/sass/test/sass/results/expanded.css +19 -0
- data/vendor/sass/test/sass/results/if.css +3 -0
- data/vendor/sass/test/sass/results/import.css +31 -0
- data/vendor/sass/test/sass/results/import_charset.css +4 -0
- data/vendor/sass/test/sass/results/import_charset_1_8.css +4 -0
- data/vendor/sass/test/sass/results/import_charset_ibm866.css +4 -0
- data/vendor/sass/test/sass/results/line_numbers.css +49 -0
- data/vendor/sass/test/sass/results/mixins.css +95 -0
- data/vendor/sass/test/sass/results/multiline.css +24 -0
- data/vendor/sass/test/sass/results/nested.css +22 -0
- data/vendor/sass/test/sass/results/options.css +1 -0
- data/vendor/sass/test/sass/results/parent_ref.css +13 -0
- data/vendor/sass/test/sass/results/script.css +16 -0
- data/vendor/sass/test/sass/results/scss_import.css +31 -0
- data/vendor/sass/test/sass/results/scss_importee.css +2 -0
- data/vendor/sass/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/vendor/sass/test/sass/results/subdir/subdir.css +3 -0
- data/vendor/sass/test/sass/results/units.css +11 -0
- data/vendor/sass/test/sass/results/warn.css +0 -0
- data/vendor/sass/test/sass/results/warn_imported.css +0 -0
- data/vendor/sass/test/sass/script_conversion_test.rb +283 -0
- data/vendor/sass/test/sass/script_test.rb +496 -0
- data/vendor/sass/test/sass/scss/css_test.rb +916 -0
- data/vendor/sass/test/sass/scss/rx_test.rb +156 -0
- data/vendor/sass/test/sass/scss/scss_test.rb +1249 -0
- data/vendor/sass/test/sass/scss/test_helper.rb +37 -0
- data/vendor/sass/test/sass/templates/_imported_charset_ibm866.sass +4 -0
- data/vendor/sass/test/sass/templates/_imported_charset_utf8.sass +4 -0
- data/vendor/sass/test/sass/templates/_partial.sass +2 -0
- data/vendor/sass/test/sass/templates/alt.sass +16 -0
- data/vendor/sass/test/sass/templates/basic.sass +23 -0
- data/vendor/sass/test/sass/templates/bork1.sass +2 -0
- data/vendor/sass/test/sass/templates/bork2.sass +2 -0
- data/vendor/sass/test/sass/templates/bork3.sass +2 -0
- data/vendor/sass/test/sass/templates/bork4.sass +2 -0
- data/vendor/sass/test/sass/templates/compact.sass +17 -0
- data/vendor/sass/test/sass/templates/complex.sass +305 -0
- data/vendor/sass/test/sass/templates/compressed.sass +15 -0
- data/vendor/sass/test/sass/templates/expanded.sass +17 -0
- data/vendor/sass/test/sass/templates/if.sass +11 -0
- data/vendor/sass/test/sass/templates/import.sass +12 -0
- data/vendor/sass/test/sass/templates/import_charset.sass +7 -0
- data/vendor/sass/test/sass/templates/import_charset_1_8.sass +4 -0
- data/vendor/sass/test/sass/templates/import_charset_ibm866.sass +9 -0
- data/vendor/sass/test/sass/templates/importee.less +2 -0
- data/vendor/sass/test/sass/templates/importee.sass +19 -0
- data/vendor/sass/test/sass/templates/line_numbers.sass +13 -0
- data/vendor/sass/test/sass/templates/mixin_bork.sass +5 -0
- data/vendor/sass/test/sass/templates/mixins.sass +76 -0
- data/vendor/sass/test/sass/templates/multiline.sass +20 -0
- data/vendor/sass/test/sass/templates/nested.sass +25 -0
- data/vendor/sass/test/sass/templates/nested_bork1.sass +2 -0
- data/vendor/sass/test/sass/templates/nested_bork2.sass +2 -0
- data/vendor/sass/test/sass/templates/nested_bork3.sass +2 -0
- data/vendor/sass/test/sass/templates/nested_bork4.sass +2 -0
- data/vendor/sass/test/sass/templates/nested_import.sass +2 -0
- data/vendor/sass/test/sass/templates/nested_mixin_bork.sass +6 -0
- data/vendor/sass/test/sass/templates/options.sass +2 -0
- data/vendor/sass/test/sass/templates/parent_ref.sass +25 -0
- data/vendor/sass/test/sass/templates/script.sass +101 -0
- data/vendor/sass/test/sass/templates/scss_import.scss +11 -0
- data/vendor/sass/test/sass/templates/scss_importee.scss +1 -0
- data/vendor/sass/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/vendor/sass/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/vendor/sass/test/sass/templates/subdir/subdir.sass +6 -0
- data/vendor/sass/test/sass/templates/units.sass +11 -0
- data/vendor/sass/test/sass/templates/warn.sass +3 -0
- data/vendor/sass/test/sass/templates/warn_imported.sass +4 -0
- data/vendor/sass/test/sass/test_helper.rb +8 -0
- data/vendor/sass/test/sass/util/subset_map_test.rb +91 -0
- data/vendor/sass/test/sass/util_test.rb +254 -0
- data/vendor/sass/test/test_helper.rb +69 -0
- data/vendor/sass/yard/callbacks.rb +29 -0
- data/vendor/sass/yard/default/fulldoc/html/css/common.sass +26 -0
- data/vendor/sass/yard/default/layout/html/footer.erb +12 -0
- data/vendor/sass/yard/inherited_hash.rb +41 -0
- metadata +458 -0
data/lib/haml/parser.rb
ADDED
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
require 'haml/shared'
|
|
3
|
+
|
|
4
|
+
module Haml
|
|
5
|
+
module Parser
|
|
6
|
+
include Haml::Util
|
|
7
|
+
|
|
8
|
+
# Designates an XHTML/XML element.
|
|
9
|
+
ELEMENT = ?%
|
|
10
|
+
|
|
11
|
+
# Designates a `<div>` element with the given class.
|
|
12
|
+
DIV_CLASS = ?.
|
|
13
|
+
|
|
14
|
+
# Designates a `<div>` element with the given id.
|
|
15
|
+
DIV_ID = ?#
|
|
16
|
+
|
|
17
|
+
# Designates an XHTML/XML comment.
|
|
18
|
+
COMMENT = ?/
|
|
19
|
+
|
|
20
|
+
# Designates an XHTML doctype or script that is never HTML-escaped.
|
|
21
|
+
DOCTYPE = ?!
|
|
22
|
+
|
|
23
|
+
# Designates script, the result of which is output.
|
|
24
|
+
SCRIPT = ?=
|
|
25
|
+
|
|
26
|
+
# Designates script that is always HTML-escaped.
|
|
27
|
+
SANITIZE = ?&
|
|
28
|
+
|
|
29
|
+
# Designates script, the result of which is flattened and output.
|
|
30
|
+
FLAT_SCRIPT = ?~
|
|
31
|
+
|
|
32
|
+
# Designates script which is run but not output.
|
|
33
|
+
SILENT_SCRIPT = ?-
|
|
34
|
+
|
|
35
|
+
# When following SILENT_SCRIPT, designates a comment that is not output.
|
|
36
|
+
SILENT_COMMENT = ?#
|
|
37
|
+
|
|
38
|
+
# Designates a non-parsed line.
|
|
39
|
+
ESCAPE = ?\\
|
|
40
|
+
|
|
41
|
+
# Designates a block of filtered text.
|
|
42
|
+
FILTER = ?:
|
|
43
|
+
|
|
44
|
+
# Designates a non-parsed line. Not actually a character.
|
|
45
|
+
PLAIN_TEXT = -1
|
|
46
|
+
|
|
47
|
+
# Designates a piece of client-side ejs functionality
|
|
48
|
+
HAML_EJS = "^"
|
|
49
|
+
# Designates a client-side interpolated ejs value
|
|
50
|
+
HAML_EJS_INTERPOLATE = /^#{Regexp.escape(HAML_EJS)}=\s+(.*)$/
|
|
51
|
+
# Designates a client-side conditional ejs block
|
|
52
|
+
HAML_EJS_CONDITIONAL = /^#{Regexp.escape(HAML_EJS)}if\s+(.*)$/
|
|
53
|
+
# Designates a client-side negated conditional ejs block
|
|
54
|
+
HAML_EJS_NEGATED_CONDITIONAL = /^#{Regexp.escape(HAML_EJS)}unless\s+(.*)$/
|
|
55
|
+
# Designates a client-side negated conditional ejs block
|
|
56
|
+
HAML_EJS_ELSEIF_CONDITIONAL = /^#{Regexp.escape(HAML_EJS)}elsif\s+(.*)$/
|
|
57
|
+
# Designates a client-side negated conditional ejs block
|
|
58
|
+
HAML_EJS_ELSE_CONDITIONAL = /^#{Regexp.escape(HAML_EJS)}else\s*$/
|
|
59
|
+
# Designates a client-side iteration ejs block
|
|
60
|
+
HAML_EJS_ITERATE = /^#{Regexp.escape(HAML_EJS)}each\s+(.*)$/
|
|
61
|
+
|
|
62
|
+
# Keeps track of the ASCII values of the characters that begin a
|
|
63
|
+
# specially-interpreted line.
|
|
64
|
+
SPECIAL_CHARACTERS = [
|
|
65
|
+
ELEMENT,
|
|
66
|
+
DIV_CLASS,
|
|
67
|
+
DIV_ID,
|
|
68
|
+
COMMENT,
|
|
69
|
+
DOCTYPE,
|
|
70
|
+
SCRIPT,
|
|
71
|
+
SANITIZE,
|
|
72
|
+
FLAT_SCRIPT,
|
|
73
|
+
SILENT_SCRIPT,
|
|
74
|
+
ESCAPE,
|
|
75
|
+
FILTER,
|
|
76
|
+
HAML_EJS
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# The value of the character that designates that a line is part
|
|
80
|
+
# of a multiline string.
|
|
81
|
+
MULTILINE_CHAR_VALUE = ?|
|
|
82
|
+
|
|
83
|
+
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
|
84
|
+
START_BLOCK_KEYWORDS = %w[if begin case]
|
|
85
|
+
# Try to parse assignments to block starters as best as possible
|
|
86
|
+
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
|
87
|
+
BLOCK_KEYWORD_REGEX = /^-\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
|
88
|
+
HAML_EJS_BLOCK_KEYWORD_REGEX = /^#{Regexp.escape(HAML_EJS)}(if|else|elsif|unless)\s*/
|
|
89
|
+
|
|
90
|
+
# The Regex that matches a Doctype command.
|
|
91
|
+
DOCTYPE_REGEX = /(\d(?:\.\d)?)?[\s]*([a-z]*)\s*([^ ]+)?/i
|
|
92
|
+
|
|
93
|
+
# The Regex that matches a literal string or symbol value
|
|
94
|
+
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2/
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# @private
|
|
99
|
+
class Line < Struct.new(:text, :unstripped, :full, :index, :compiler, :eod)
|
|
100
|
+
alias_method :eod?, :eod
|
|
101
|
+
|
|
102
|
+
# @private
|
|
103
|
+
def tabs
|
|
104
|
+
line = self
|
|
105
|
+
@tabs ||= compiler.instance_eval do
|
|
106
|
+
break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
|
|
107
|
+
|
|
108
|
+
if @indentation.nil?
|
|
109
|
+
@indentation = whitespace
|
|
110
|
+
|
|
111
|
+
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
|
112
|
+
raise SyntaxError.new("Indentation can't use both tabs and spaces.", line.index)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
|
116
|
+
break 1
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
tabs = whitespace.length / @indentation.length
|
|
120
|
+
break tabs if whitespace == @indentation * tabs
|
|
121
|
+
break @template_tabs + 1 if flat? && whitespace =~ /^#{@flat_spaces}/
|
|
122
|
+
|
|
123
|
+
raise SyntaxError.new(<<END.strip.gsub("\n", ' '), line.index)
|
|
124
|
+
Inconsistent indentation: #{Haml::Shared.human_indentation whitespace, true} used for indentation,
|
|
125
|
+
but the rest of the document was indented using #{Haml::Shared.human_indentation @indentation}.
|
|
126
|
+
END
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @private
|
|
132
|
+
class ParseNode < Struct.new(:type, :line, :value, :parent, :children)
|
|
133
|
+
def initialize(*args)
|
|
134
|
+
super
|
|
135
|
+
self.children ||= []
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def inspect
|
|
139
|
+
text = "(#{type} #{value.inspect}"
|
|
140
|
+
children.each {|c| text << "\n" << c.inspect.gsub(/^/, " ")}
|
|
141
|
+
text + ")"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def parse
|
|
146
|
+
@root = @parent = ParseNode.new(:root)
|
|
147
|
+
@haml_comment = false
|
|
148
|
+
@indentation = nil
|
|
149
|
+
@line = next_line
|
|
150
|
+
|
|
151
|
+
raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0
|
|
152
|
+
|
|
153
|
+
while next_line
|
|
154
|
+
process_indent(@line) unless @line.text.empty?
|
|
155
|
+
|
|
156
|
+
if flat?
|
|
157
|
+
text = @line.full.dup
|
|
158
|
+
text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
|
|
159
|
+
@filter_buffer << "#{text}\n"
|
|
160
|
+
@line = @next_line
|
|
161
|
+
next
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@tab_up = nil
|
|
165
|
+
process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
|
|
166
|
+
if @parent.type != :haml_comment && (block_opened? || @tab_up)
|
|
167
|
+
@template_tabs += 1
|
|
168
|
+
@parent = @parent.children.last
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if !flat? && @next_line.tabs - @line.tabs > 1
|
|
172
|
+
raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
@line = @next_line
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Close all the open tags
|
|
179
|
+
close until @parent.type == :root
|
|
180
|
+
@root
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Processes and deals with lowering indentation.
|
|
184
|
+
def process_indent(line)
|
|
185
|
+
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
|
186
|
+
|
|
187
|
+
to_close = @template_tabs - line.tabs
|
|
188
|
+
to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Processes a single line of Haml.
|
|
192
|
+
#
|
|
193
|
+
# This method doesn't return anything; it simply processes the line and
|
|
194
|
+
# adds the appropriate code to `@precompiled`.
|
|
195
|
+
def process_line(text, index)
|
|
196
|
+
@index = index + 1
|
|
197
|
+
|
|
198
|
+
case text[0]
|
|
199
|
+
when DIV_CLASS; push div(text)
|
|
200
|
+
when DIV_ID
|
|
201
|
+
return push plain(text) if text[1] == ?{
|
|
202
|
+
push div(text)
|
|
203
|
+
when ELEMENT; push tag(text)
|
|
204
|
+
when COMMENT; push comment(text[1..-1].strip)
|
|
205
|
+
when SANITIZE
|
|
206
|
+
return push plain(text[3..-1].strip, :escape_html) if text[1..2] == "=="
|
|
207
|
+
return push script(text[2..-1].strip, :escape_html) if text[1] == SCRIPT
|
|
208
|
+
return push flat_script(text[2..-1].strip, :escape_html) if text[1] == FLAT_SCRIPT
|
|
209
|
+
return push plain(text[1..-1].strip, :escape_html) if text[1] == ?\s
|
|
210
|
+
push plain(text)
|
|
211
|
+
when SCRIPT
|
|
212
|
+
return push plain(text[2..-1].strip) if text[1] == SCRIPT
|
|
213
|
+
push script(text[1..-1])
|
|
214
|
+
when FLAT_SCRIPT; push flat_script(text[1..-1])
|
|
215
|
+
when SILENT_SCRIPT; push silent_script(text)
|
|
216
|
+
when FILTER; push filter(text[1..-1].downcase)
|
|
217
|
+
when DOCTYPE
|
|
218
|
+
return push doctype(text) if text[0...3] == '!!!'
|
|
219
|
+
return push plain(text[3..-1].strip, !:escape_html) if text[1..2] == "=="
|
|
220
|
+
return push script(text[2..-1].strip, !:escape_html) if text[1] == SCRIPT
|
|
221
|
+
return push flat_script(text[2..-1].strip, !:escape_html) if text[1] == FLAT_SCRIPT
|
|
222
|
+
return push plain(text[1..-1].strip, !:escape_html) if text[1] == ?\s
|
|
223
|
+
push plain(text)
|
|
224
|
+
when ESCAPE; push plain(text[1..-1])
|
|
225
|
+
when HAML_EJS; push haml_ejs(text)
|
|
226
|
+
else; push plain(text)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def block_keyword(text)
|
|
231
|
+
if HamlEjs.enabled
|
|
232
|
+
haml_ejs_keyword = text.scan(HAML_EJS_BLOCK_KEYWORD_REGEX)[0]
|
|
233
|
+
return haml_ejs_keyword[0] if haml_ejs_keyword
|
|
234
|
+
end
|
|
235
|
+
return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
|
|
236
|
+
keyword[0] || keyword[1]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def mid_block_keyword?(text)
|
|
240
|
+
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def push(node)
|
|
244
|
+
@parent.children << node
|
|
245
|
+
node.parent = @parent
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def plain(text, escape_html = nil)
|
|
249
|
+
if block_opened?
|
|
250
|
+
raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
unless contains_interpolation?(text)
|
|
254
|
+
return ParseNode.new(:plain, @index, :text => text)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
escape_html = @options[:escape_html] if escape_html.nil?
|
|
258
|
+
script(unescape_interpolation(text, escape_html), !:escape_html)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def script(text, escape_html = nil, preserve = false)
|
|
262
|
+
raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
|
|
263
|
+
text = handle_ruby_multiline(text)
|
|
264
|
+
escape_html = @options[:escape_html] if escape_html.nil?
|
|
265
|
+
|
|
266
|
+
ParseNode.new(:script, @index, :text => text, :escape_html => escape_html,
|
|
267
|
+
:preserve => preserve)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def flat_script(text, escape_html = nil)
|
|
271
|
+
raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
|
|
272
|
+
script(text, escape_html, :preserve)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def silent_script(text)
|
|
276
|
+
return haml_comment(text[2..-1]) if text[1] == SILENT_COMMENT
|
|
277
|
+
|
|
278
|
+
raise SyntaxError.new(<<END.rstrip, @index - 1) if text[1..-1].strip == "end"
|
|
279
|
+
You don't need to use "- end" in Haml. Un-indent to close a block:
|
|
280
|
+
- if foo?
|
|
281
|
+
%strong Foo!
|
|
282
|
+
- else
|
|
283
|
+
Not foo.
|
|
284
|
+
%p This line is un-indented, so it isn't part of the "if" block
|
|
285
|
+
END
|
|
286
|
+
|
|
287
|
+
text = handle_ruby_multiline(text)
|
|
288
|
+
keyword = block_keyword(text)
|
|
289
|
+
|
|
290
|
+
@tab_up = ["if", "case"].include?(keyword)
|
|
291
|
+
ParseNode.new(:silent_script, @index,
|
|
292
|
+
:text => text[1..-1], :keyword => keyword)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def haml_comment(text)
|
|
296
|
+
@haml_comment = block_opened?
|
|
297
|
+
ParseNode.new(:haml_comment, @index, :text => text)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def tag(line)
|
|
301
|
+
tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
|
302
|
+
nuke_inner_whitespace, action, value, last_line = parse_tag(line)
|
|
303
|
+
|
|
304
|
+
preserve_tag = @options[:preserve].include?(tag_name)
|
|
305
|
+
nuke_inner_whitespace ||= preserve_tag
|
|
306
|
+
preserve_tag = false if @options[:ugly]
|
|
307
|
+
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
|
308
|
+
|
|
309
|
+
case action
|
|
310
|
+
when '/'; self_closing = true
|
|
311
|
+
when '~'; parse = preserve_script = true
|
|
312
|
+
when '='
|
|
313
|
+
parse = true
|
|
314
|
+
if value[0] == ?=
|
|
315
|
+
value = unescape_interpolation(value[1..-1].strip, escape_html)
|
|
316
|
+
escape_html = false
|
|
317
|
+
end
|
|
318
|
+
when '&', '!'
|
|
319
|
+
if value[0] == ?= || value[0] == ?~
|
|
320
|
+
parse = true
|
|
321
|
+
preserve_script = (value[0] == ?~)
|
|
322
|
+
if value[1] == ?=
|
|
323
|
+
value = unescape_interpolation(value[2..-1].strip, escape_html)
|
|
324
|
+
escape_html = false
|
|
325
|
+
else
|
|
326
|
+
value = value[1..-1].strip
|
|
327
|
+
end
|
|
328
|
+
elsif contains_interpolation?(value)
|
|
329
|
+
value = unescape_interpolation(value, escape_html)
|
|
330
|
+
parse = true
|
|
331
|
+
escape_html = false
|
|
332
|
+
end
|
|
333
|
+
else
|
|
334
|
+
if contains_interpolation?(value)
|
|
335
|
+
value = unescape_interpolation(value, escape_html)
|
|
336
|
+
parse = true
|
|
337
|
+
escape_html = false
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
attributes = Parser.parse_class_and_id(attributes)
|
|
342
|
+
attributes_list = []
|
|
343
|
+
|
|
344
|
+
if attributes_hashes[:new]
|
|
345
|
+
static_attributes, attributes_hash = attributes_hashes[:new]
|
|
346
|
+
Buffer.merge_attrs(attributes, static_attributes) if static_attributes
|
|
347
|
+
attributes_list << attributes_hash
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
if attributes_hashes[:old]
|
|
351
|
+
static_attributes = parse_static_hash(attributes_hashes[:old])
|
|
352
|
+
Buffer.merge_attrs(attributes, static_attributes) if static_attributes
|
|
353
|
+
attributes_list << attributes_hashes[:old] unless static_attributes || @options[:suppress_eval]
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
attributes_list.compact!
|
|
357
|
+
|
|
358
|
+
raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
|
|
359
|
+
raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
|
|
360
|
+
raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
|
|
361
|
+
|
|
362
|
+
if block_opened? && !value.empty? && !is_ruby_multiline?(value)
|
|
363
|
+
raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", @next_line.index)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
|
|
367
|
+
value = nil if value.empty? && (block_opened? || self_closing)
|
|
368
|
+
value = handle_ruby_multiline(value) if parse
|
|
369
|
+
|
|
370
|
+
ParseNode.new(:tag, @index, :name => tag_name, :attributes => attributes,
|
|
371
|
+
:attributes_hashes => attributes_list, :self_closing => self_closing,
|
|
372
|
+
:nuke_inner_whitespace => nuke_inner_whitespace,
|
|
373
|
+
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
|
374
|
+
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
|
375
|
+
:preserve_script => preserve_script, :parse => parse, :value => value)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Renders a line that creates an XHTML tag and has an implicit div because of
|
|
379
|
+
# `.` or `#`.
|
|
380
|
+
def div(line)
|
|
381
|
+
tag('%div' + line)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Renders an XHTML comment.
|
|
385
|
+
def comment(line)
|
|
386
|
+
conditional, line = balance(line, ?[, ?]) if line[0] == ?[
|
|
387
|
+
line.strip!
|
|
388
|
+
conditional << ">" if conditional
|
|
389
|
+
|
|
390
|
+
if block_opened? && !line.empty?
|
|
391
|
+
raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
ParseNode.new(:comment, @index, :conditional => conditional, :text => line)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Renders an XHTML doctype or XML shebang.
|
|
398
|
+
def doctype(line)
|
|
399
|
+
raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
|
|
400
|
+
version, type, encoding = line[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
|
401
|
+
ParseNode.new(:doctype, @index, :version => version, :type => type, :encoding => encoding)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def filter(name)
|
|
405
|
+
raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
|
|
406
|
+
|
|
407
|
+
@filter_buffer = String.new
|
|
408
|
+
|
|
409
|
+
if filter_opened?
|
|
410
|
+
@flat = true
|
|
411
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
|
412
|
+
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
ParseNode.new(:filter, @index, :name => name, :text => @filter_buffer)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def close
|
|
419
|
+
node, @parent = @parent, @parent.parent
|
|
420
|
+
@template_tabs -= 1
|
|
421
|
+
send("close_#{node.type}", node) if respond_to?("close_#{node.type}", :include_private)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def haml_ejs text
|
|
425
|
+
return plain(text) unless HamlEjs.enabled
|
|
426
|
+
case text
|
|
427
|
+
when HAML_EJS_INTERPOLATE; haml_ejs_interpolate($1)
|
|
428
|
+
when HAML_EJS_CONDITIONAL; haml_ejs_conditional($1)
|
|
429
|
+
# when HAML_EJS_NEGATED_CONDITIONAL; haml_ejs_negated_conditional($1)
|
|
430
|
+
when HAML_EJS_ELSE_CONDITIONAL; haml_ejs_conditional($1, :else => true)
|
|
431
|
+
when HAML_EJS_ELSEIF_CONDITIONAL; haml_ejs_conditional($1, :else => true)
|
|
432
|
+
when HAML_EJS_NEGATED_CONDITIONAL; haml_ejs_conditional($1, :negate => true)
|
|
433
|
+
when HAML_EJS_ITERATE; haml_ejs_iterate($1)
|
|
434
|
+
else
|
|
435
|
+
raise SyntaxError.new("Unknow HamlEjs sigil #{text}")
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def haml_ejs_interpolate text
|
|
440
|
+
ParseNode.new(:ejs_interpolate, @index, :text => text)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def haml_ejs_conditional text, options = {}
|
|
444
|
+
ParseNode.new(:ejs_conditional, @index, options.merge(:expression => text))
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def haml_ejs_iterate text
|
|
448
|
+
ParseNode.new(:ejs_iterate, @index, :collection => text)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def close_filter(_)
|
|
452
|
+
@flat = false
|
|
453
|
+
@flat_spaces = nil
|
|
454
|
+
@filter_buffer = nil
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def close_haml_comment(_)
|
|
458
|
+
@haml_comment = false
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def close_silent_script(node)
|
|
462
|
+
# Post-process case statements to normalize the nesting of "when" clauses
|
|
463
|
+
return unless node.value[:keyword] == "case"
|
|
464
|
+
return unless first = node.children.first
|
|
465
|
+
return unless first.type == :silent_script && first.value[:keyword] == "when"
|
|
466
|
+
return if first.children.empty?
|
|
467
|
+
# If the case node has a "when" child with children, it's the
|
|
468
|
+
# only child. Then we want to put everything nested beneath it
|
|
469
|
+
# beneath the case itself (just like "if").
|
|
470
|
+
node.children = [first, *first.children]
|
|
471
|
+
first.children = []
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# This is a class method so it can be accessed from {Haml::Helpers}.
|
|
475
|
+
#
|
|
476
|
+
# Iterates through the classes and ids supplied through `.`
|
|
477
|
+
# and `#` syntax, and returns a hash with them as attributes,
|
|
478
|
+
# that can then be merged with another attributes hash.
|
|
479
|
+
def self.parse_class_and_id(list)
|
|
480
|
+
attributes = {}
|
|
481
|
+
list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
|
|
482
|
+
case type
|
|
483
|
+
when '.'
|
|
484
|
+
if attributes['class']
|
|
485
|
+
attributes['class'] += " "
|
|
486
|
+
else
|
|
487
|
+
attributes['class'] = ""
|
|
488
|
+
end
|
|
489
|
+
attributes['class'] += property
|
|
490
|
+
when '#'; attributes['id'] = property
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
attributes
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def parse_static_hash(text)
|
|
497
|
+
attributes = {}
|
|
498
|
+
scanner = StringScanner.new(text)
|
|
499
|
+
scanner.scan(/\s+/)
|
|
500
|
+
until scanner.eos?
|
|
501
|
+
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
|
502
|
+
return unless scanner.scan(/\s*=>\s*/)
|
|
503
|
+
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
|
504
|
+
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
|
505
|
+
attributes[eval(key).to_s] = eval(value).to_s
|
|
506
|
+
end
|
|
507
|
+
attributes
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
|
511
|
+
def parse_tag(line)
|
|
512
|
+
raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
|
|
513
|
+
|
|
514
|
+
tag_name, attributes, rest = match
|
|
515
|
+
raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
|
|
516
|
+
|
|
517
|
+
new_attributes_hash = old_attributes_hash = last_line = nil
|
|
518
|
+
object_ref = "nil"
|
|
519
|
+
attributes_hashes = {}
|
|
520
|
+
while rest
|
|
521
|
+
case rest[0]
|
|
522
|
+
when ?{
|
|
523
|
+
break if old_attributes_hash
|
|
524
|
+
old_attributes_hash, rest, last_line = parse_old_attributes(rest)
|
|
525
|
+
attributes_hashes[:old] = old_attributes_hash
|
|
526
|
+
when ?(
|
|
527
|
+
break if new_attributes_hash
|
|
528
|
+
new_attributes_hash, rest, last_line = parse_new_attributes(rest)
|
|
529
|
+
attributes_hashes[:new] = new_attributes_hash
|
|
530
|
+
when ?[
|
|
531
|
+
break unless object_ref == "nil"
|
|
532
|
+
object_ref, rest = balance(rest, ?[, ?])
|
|
533
|
+
else; break
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
if rest
|
|
538
|
+
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
|
539
|
+
nuke_whitespace ||= ''
|
|
540
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
|
541
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
value = value.to_s.strip
|
|
545
|
+
[tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
|
546
|
+
nuke_inner_whitespace, action, value, last_line || @index]
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def parse_old_attributes(line)
|
|
550
|
+
line = line.dup
|
|
551
|
+
last_line = @index
|
|
552
|
+
|
|
553
|
+
begin
|
|
554
|
+
attributes_hash, rest = balance(line, ?{, ?})
|
|
555
|
+
rescue SyntaxError => e
|
|
556
|
+
if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
|
|
557
|
+
line << "\n" << @next_line.text
|
|
558
|
+
last_line += 1
|
|
559
|
+
next_line
|
|
560
|
+
retry
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
raise e
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
|
567
|
+
return attributes_hash, rest, last_line
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def parse_new_attributes(line)
|
|
571
|
+
line = line.dup
|
|
572
|
+
scanner = StringScanner.new(line)
|
|
573
|
+
last_line = @index
|
|
574
|
+
attributes = {}
|
|
575
|
+
|
|
576
|
+
scanner.scan(/\(\s*/)
|
|
577
|
+
loop do
|
|
578
|
+
name, value = parse_new_attribute(scanner)
|
|
579
|
+
break if name.nil?
|
|
580
|
+
|
|
581
|
+
if name == false
|
|
582
|
+
text = (Haml::Shared.balance(line, ?(, ?)) || [line]).first
|
|
583
|
+
raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1)
|
|
584
|
+
end
|
|
585
|
+
attributes[name] = value
|
|
586
|
+
scanner.scan(/\s*/)
|
|
587
|
+
|
|
588
|
+
if scanner.eos?
|
|
589
|
+
line << " " << @next_line.text
|
|
590
|
+
last_line += 1
|
|
591
|
+
next_line
|
|
592
|
+
scanner.scan(/\s*/)
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
static_attributes = {}
|
|
597
|
+
dynamic_attributes = "{"
|
|
598
|
+
attributes.each do |name, (type, val)|
|
|
599
|
+
if type == :static
|
|
600
|
+
static_attributes[name] = val
|
|
601
|
+
else
|
|
602
|
+
dynamic_attributes << inspect_obj(name) << " => " << val << ","
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
dynamic_attributes << "}"
|
|
606
|
+
dynamic_attributes = nil if dynamic_attributes == "{}"
|
|
607
|
+
|
|
608
|
+
return [static_attributes, dynamic_attributes], scanner.rest, last_line
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def parse_new_attribute(scanner)
|
|
612
|
+
unless name = scanner.scan(/[-:\w]+/)
|
|
613
|
+
return if scanner.scan(/\)/)
|
|
614
|
+
return false
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
scanner.scan(/\s*/)
|
|
618
|
+
return name, [:static, true] unless scanner.scan(/=/) #/end
|
|
619
|
+
|
|
620
|
+
scanner.scan(/\s*/)
|
|
621
|
+
unless quote = scanner.scan(/["']/)
|
|
622
|
+
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
|
623
|
+
return name, [:dynamic, var]
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
|
|
627
|
+
content = []
|
|
628
|
+
loop do
|
|
629
|
+
return false unless scanner.scan(re)
|
|
630
|
+
content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
|
|
631
|
+
break if scanner[2] == quote
|
|
632
|
+
content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
return name, [:static, content.first[1]] if content.size == 1
|
|
636
|
+
return name, [:dynamic,
|
|
637
|
+
'"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}"}.join + '"']
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def raw_next_line
|
|
641
|
+
text = @template.shift
|
|
642
|
+
return unless text
|
|
643
|
+
|
|
644
|
+
index = @template_index
|
|
645
|
+
@template_index += 1
|
|
646
|
+
|
|
647
|
+
return text, index
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
def next_line
|
|
651
|
+
text, index = raw_next_line
|
|
652
|
+
return unless text
|
|
653
|
+
|
|
654
|
+
# :eod is a special end-of-document marker
|
|
655
|
+
line =
|
|
656
|
+
if text == :eod
|
|
657
|
+
Line.new '-#', '-#', '-#', index, self, true
|
|
658
|
+
else
|
|
659
|
+
Line.new text.strip, text.lstrip.chomp, text, index, self, false
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# `flat?' here is a little outdated,
|
|
663
|
+
# so we have to manually check if either the previous or current line
|
|
664
|
+
# closes the flat block, as well as whether a new block is opened.
|
|
665
|
+
@line.tabs if @line
|
|
666
|
+
unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
|
|
667
|
+
(@line && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
|
|
668
|
+
return next_line if line.text.empty?
|
|
669
|
+
|
|
670
|
+
handle_multiline(line)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
@next_line = line
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def closes_flat?(line)
|
|
677
|
+
line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def un_next_line(line)
|
|
681
|
+
@template.unshift line
|
|
682
|
+
@template_index -= 1
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def handle_multiline(line)
|
|
686
|
+
return unless is_multiline?(line.text)
|
|
687
|
+
line.text.slice!(-1)
|
|
688
|
+
while new_line = raw_next_line.first
|
|
689
|
+
break if new_line == :eod
|
|
690
|
+
next if new_line.strip.empty?
|
|
691
|
+
break unless is_multiline?(new_line.strip)
|
|
692
|
+
line.text << new_line.strip[0...-1]
|
|
693
|
+
end
|
|
694
|
+
un_next_line new_line
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Checks whether or not `line` is in a multiline sequence.
|
|
698
|
+
def is_multiline?(text)
|
|
699
|
+
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def handle_ruby_multiline(text)
|
|
703
|
+
text = text.rstrip
|
|
704
|
+
return text unless is_ruby_multiline?(text)
|
|
705
|
+
un_next_line @next_line.full
|
|
706
|
+
begin
|
|
707
|
+
new_line = raw_next_line.first
|
|
708
|
+
break if new_line == :eod
|
|
709
|
+
next if new_line.strip.empty?
|
|
710
|
+
text << " " << new_line.strip
|
|
711
|
+
end while is_ruby_multiline?(new_line.strip)
|
|
712
|
+
next_line
|
|
713
|
+
text
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def is_ruby_multiline?(text)
|
|
717
|
+
text && text.length > 1 && text[-1] == ?, && text[-2] != ?? && text[-3..-2] != "?\\"
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def contains_interpolation?(str)
|
|
721
|
+
str.include?('#{')
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def unescape_interpolation(str, escape_html = nil)
|
|
725
|
+
res = ''
|
|
726
|
+
rest = Haml::Shared.handle_interpolation str.dump do |scan|
|
|
727
|
+
escapes = (scan[2].size - 1) / 2
|
|
728
|
+
res << scan.matched[0...-3 - escapes]
|
|
729
|
+
if escapes % 2 == 1
|
|
730
|
+
res << '#{'
|
|
731
|
+
else
|
|
732
|
+
content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"')
|
|
733
|
+
content = "Haml::Helpers.html_escape((#{content}))" if escape_html
|
|
734
|
+
res << '#{' + content + "}"# Use eval to get rid of string escapes
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
res + rest
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
def balance(*args)
|
|
741
|
+
res = Haml::Shared.balance(*args)
|
|
742
|
+
return res if res
|
|
743
|
+
raise SyntaxError.new("Unbalanced brackets.")
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def block_opened?
|
|
747
|
+
@next_line.tabs > @line.tabs
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# Same semantics as block_opened?, except that block_opened? uses Line#tabs,
|
|
751
|
+
# which doesn't interact well with filter lines
|
|
752
|
+
def filter_opened?
|
|
753
|
+
@next_line.full =~ (@indentation ? /^#{@indentation * @template_tabs}/ : /^\s/)
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
def flat?
|
|
757
|
+
@flat
|
|
758
|
+
end
|
|
759
|
+
end
|
|
760
|
+
end
|