hamlit 1.7.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (283) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +25 -37
  5. data/CHANGELOG.md +18 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +23 -2
  8. data/README.md +106 -48
  9. data/REFERENCE.md +222 -0
  10. data/Rakefile +77 -19
  11. data/benchmark/boolean_attribute.haml +6 -0
  12. data/benchmark/class_attribute.haml +5 -0
  13. data/benchmark/common_attribute.haml +3 -0
  14. data/benchmark/data_attribute.haml +4 -0
  15. data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
  16. data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
  17. data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
  18. data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
  19. data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
  20. data/benchmark/etc/attribute_builder.haml +5 -0
  21. data/benchmark/etc/real_sample.haml +888 -0
  22. data/benchmark/etc/real_sample.rb +11 -0
  23. data/benchmark/etc/static_analyzer.haml +1 -0
  24. data/benchmark/etc/tags.haml +3 -0
  25. data/benchmark/ext/build_data.rb +15 -0
  26. data/benchmark/ext/build_id.rb +13 -0
  27. data/benchmark/id_attribute.haml +3 -0
  28. data/benchmark/plain.haml +4 -0
  29. data/benchmark/script.haml +4 -0
  30. data/benchmark/slim/LICENSE +21 -0
  31. data/{benchmarks → benchmark/slim}/context.rb +2 -4
  32. data/benchmark/slim/run-benchmarks.rb +94 -0
  33. data/{benchmarks → benchmark/slim}/view.erb +3 -3
  34. data/{benchmarks → benchmark/slim}/view.haml +0 -0
  35. data/{benchmarks/view.escaped.slim → benchmark/slim/view.slim} +1 -1
  36. data/benchmark/string_interpolation.haml +2 -0
  37. data/benchmark/utils/benchmark_ips_extension.rb +43 -0
  38. data/bin/bench +85 -0
  39. data/bin/clone +14 -0
  40. data/bin/console +11 -0
  41. data/bin/lineprof +48 -0
  42. data/bin/ruby +3 -0
  43. data/bin/setup +7 -0
  44. data/bin/stackprof +27 -0
  45. data/{test → bin/test} +6 -10
  46. data/{bin → exe}/hamlit +0 -0
  47. data/ext/hamlit/extconf.rb +14 -0
  48. data/ext/hamlit/hamlit.c +512 -0
  49. data/ext/hamlit/houdini/.gitignore +3 -0
  50. data/ext/hamlit/houdini/COPYING +7 -0
  51. data/ext/hamlit/houdini/Makefile +79 -0
  52. data/ext/hamlit/houdini/README.md +59 -0
  53. data/ext/hamlit/houdini/buffer.c +249 -0
  54. data/ext/hamlit/houdini/buffer.h +113 -0
  55. data/ext/hamlit/houdini/houdini.h +46 -0
  56. data/ext/hamlit/houdini/houdini_href_e.c +115 -0
  57. data/ext/hamlit/houdini/houdini_html_e.c +90 -0
  58. data/ext/hamlit/houdini/houdini_html_u.c +122 -0
  59. data/ext/hamlit/houdini/houdini_js_e.c +90 -0
  60. data/ext/hamlit/houdini/houdini_js_u.c +60 -0
  61. data/ext/hamlit/houdini/houdini_uri_e.c +107 -0
  62. data/ext/hamlit/houdini/houdini_uri_u.c +68 -0
  63. data/ext/hamlit/houdini/houdini_xml_e.c +136 -0
  64. data/ext/hamlit/houdini/html_unescape.gperf +258 -0
  65. data/ext/hamlit/houdini/html_unescape.h +754 -0
  66. data/ext/hamlit/houdini/tools/build_table.py +13 -0
  67. data/ext/hamlit/houdini/tools/build_tables.c +51 -0
  68. data/ext/hamlit/houdini/tools/wikipedia_table.txt +2025 -0
  69. data/hamlit.gemspec +30 -31
  70. data/lib/hamlit.rb +3 -1
  71. data/lib/hamlit/attribute_builder.rb +12 -0
  72. data/lib/hamlit/cli.rb +44 -43
  73. data/lib/hamlit/compiler.rb +92 -16
  74. data/lib/hamlit/compiler/attribute_compiler.rb +148 -0
  75. data/lib/hamlit/compiler/children_compiler.rb +111 -0
  76. data/lib/hamlit/compiler/comment_compiler.rb +36 -0
  77. data/lib/hamlit/compiler/doctype_compiler.rb +45 -0
  78. data/lib/hamlit/compiler/script_compiler.rb +97 -0
  79. data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
  80. data/lib/hamlit/compiler/tag_compiler.rb +69 -0
  81. data/lib/hamlit/engine.rb +12 -7
  82. data/lib/hamlit/error.rb +14 -0
  83. data/lib/hamlit/escapable.rb +12 -0
  84. data/lib/hamlit/filters.rb +65 -0
  85. data/lib/hamlit/filters/base.rb +4 -62
  86. data/lib/hamlit/filters/coffee.rb +9 -7
  87. data/lib/hamlit/filters/css.rb +25 -8
  88. data/lib/hamlit/filters/erb.rb +4 -6
  89. data/lib/hamlit/filters/escaped.rb +11 -9
  90. data/lib/hamlit/filters/javascript.rb +25 -8
  91. data/lib/hamlit/filters/less.rb +9 -7
  92. data/lib/hamlit/filters/markdown.rb +5 -6
  93. data/lib/hamlit/filters/plain.rb +11 -15
  94. data/lib/hamlit/filters/preserve.rb +15 -5
  95. data/lib/hamlit/filters/ruby.rb +3 -5
  96. data/lib/hamlit/filters/sass.rb +9 -7
  97. data/lib/hamlit/filters/scss.rb +9 -7
  98. data/lib/hamlit/filters/text_base.rb +24 -0
  99. data/lib/hamlit/filters/tilt_base.rb +47 -0
  100. data/lib/hamlit/hash_parser.rb +107 -0
  101. data/lib/hamlit/html.rb +9 -6
  102. data/lib/hamlit/identity.rb +12 -0
  103. data/lib/hamlit/object_ref.rb +29 -0
  104. data/lib/hamlit/parser.rb +25 -142
  105. data/lib/hamlit/parser/MIT-LICENSE +20 -0
  106. data/lib/hamlit/parser/README.md +28 -0
  107. data/lib/hamlit/parser/haml_buffer.rb +348 -0
  108. data/lib/hamlit/parser/haml_compiler.rb +553 -0
  109. data/lib/hamlit/parser/haml_error.rb +61 -0
  110. data/lib/hamlit/parser/haml_helpers.rb +727 -0
  111. data/lib/hamlit/parser/haml_options.rb +286 -0
  112. data/lib/hamlit/parser/haml_parser.rb +801 -0
  113. data/lib/hamlit/parser/haml_util.rb +283 -0
  114. data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
  115. data/lib/hamlit/{helpers.rb → rails_helpers.rb} +2 -7
  116. data/lib/hamlit/rails_template.rb +30 -0
  117. data/lib/hamlit/railtie.rb +1 -12
  118. data/lib/hamlit/ruby_expression.rb +31 -0
  119. data/lib/hamlit/static_analyzer.rb +49 -0
  120. data/lib/hamlit/string_interpolation.rb +69 -0
  121. data/lib/hamlit/template.rb +8 -0
  122. data/lib/hamlit/utils.rb +9 -0
  123. data/lib/hamlit/version.rb +1 -1
  124. metadata +116 -324
  125. data/.rspec +0 -2
  126. data/benchmarks/benchmark.rb +0 -110
  127. data/benchmarks/view.slim +0 -17
  128. data/doc/README.md +0 -19
  129. data/doc/engine/indent.md +0 -48
  130. data/doc/engine/new_attribute.md +0 -77
  131. data/doc/engine/old_attributes.md +0 -198
  132. data/doc/engine/silent_script.md +0 -97
  133. data/doc/engine/tag.md +0 -48
  134. data/doc/engine/text.md +0 -64
  135. data/doc/faml/README.md +0 -16
  136. data/doc/faml/engine/indent.md +0 -48
  137. data/doc/faml/engine/old_attributes.md +0 -111
  138. data/doc/faml/engine/silent_script.md +0 -97
  139. data/doc/faml/engine/text.md +0 -59
  140. data/doc/faml/filters/erb.md +0 -24
  141. data/doc/faml/filters/javascript.md +0 -27
  142. data/doc/faml/filters/less.md +0 -57
  143. data/doc/faml/filters/plain.md +0 -25
  144. data/doc/filters/erb.md +0 -31
  145. data/doc/filters/javascript.md +0 -83
  146. data/doc/filters/less.md +0 -57
  147. data/doc/filters/markdown.md +0 -31
  148. data/doc/filters/plain.md +0 -25
  149. data/doc/haml/README.md +0 -15
  150. data/doc/haml/engine/new_attribute.md +0 -77
  151. data/doc/haml/engine/old_attributes.md +0 -142
  152. data/doc/haml/engine/tag.md +0 -48
  153. data/doc/haml/engine/text.md +0 -29
  154. data/doc/haml/filters/erb.md +0 -26
  155. data/doc/haml/filters/javascript.md +0 -76
  156. data/doc/haml/filters/markdown.md +0 -31
  157. data/lib/hamlit/attribute.rb +0 -78
  158. data/lib/hamlit/compilers/attributes.rb +0 -108
  159. data/lib/hamlit/compilers/comment.rb +0 -13
  160. data/lib/hamlit/compilers/doctype.rb +0 -39
  161. data/lib/hamlit/compilers/filter.rb +0 -53
  162. data/lib/hamlit/compilers/new_attribute.rb +0 -115
  163. data/lib/hamlit/compilers/old_attribute.rb +0 -241
  164. data/lib/hamlit/compilers/runtime_attribute.rb +0 -58
  165. data/lib/hamlit/compilers/script.rb +0 -31
  166. data/lib/hamlit/compilers/strip.rb +0 -19
  167. data/lib/hamlit/compilers/text.rb +0 -111
  168. data/lib/hamlit/concerns/attribute_builder.rb +0 -22
  169. data/lib/hamlit/concerns/balanceable.rb +0 -68
  170. data/lib/hamlit/concerns/deprecation.rb +0 -20
  171. data/lib/hamlit/concerns/error.rb +0 -31
  172. data/lib/hamlit/concerns/escapable.rb +0 -17
  173. data/lib/hamlit/concerns/included.rb +0 -28
  174. data/lib/hamlit/concerns/indentable.rb +0 -117
  175. data/lib/hamlit/concerns/lexable.rb +0 -32
  176. data/lib/hamlit/concerns/line_reader.rb +0 -62
  177. data/lib/hamlit/concerns/registerable.rb +0 -24
  178. data/lib/hamlit/concerns/string_interpolation.rb +0 -48
  179. data/lib/hamlit/concerns/whitespace.rb +0 -91
  180. data/lib/hamlit/filters/tilt.rb +0 -41
  181. data/lib/hamlit/parsers/attribute.rb +0 -71
  182. data/lib/hamlit/parsers/comment.rb +0 -30
  183. data/lib/hamlit/parsers/doctype.rb +0 -18
  184. data/lib/hamlit/parsers/filter.rb +0 -18
  185. data/lib/hamlit/parsers/multiline.rb +0 -58
  186. data/lib/hamlit/parsers/script.rb +0 -126
  187. data/lib/hamlit/parsers/tag.rb +0 -83
  188. data/lib/hamlit/parsers/text.rb +0 -28
  189. data/lib/hamlit/temple.rb +0 -9
  190. data/release +0 -6
  191. data/spec/Rakefile +0 -72
  192. data/spec/hamlit/engine/comment_spec.rb +0 -56
  193. data/spec/hamlit/engine/doctype_spec.rb +0 -19
  194. data/spec/hamlit/engine/error_spec.rb +0 -135
  195. data/spec/hamlit/engine/indent_spec.rb +0 -42
  196. data/spec/hamlit/engine/multiline_spec.rb +0 -44
  197. data/spec/hamlit/engine/new_attribute_spec.rb +0 -110
  198. data/spec/hamlit/engine/old_attributes_spec.rb +0 -404
  199. data/spec/hamlit/engine/script_spec.rb +0 -116
  200. data/spec/hamlit/engine/silent_script_spec.rb +0 -213
  201. data/spec/hamlit/engine/tag_spec.rb +0 -295
  202. data/spec/hamlit/engine/text_spec.rb +0 -239
  203. data/spec/hamlit/engine_spec.rb +0 -58
  204. data/spec/hamlit/filters/coffee_spec.rb +0 -60
  205. data/spec/hamlit/filters/css_spec.rb +0 -33
  206. data/spec/hamlit/filters/erb_spec.rb +0 -16
  207. data/spec/hamlit/filters/javascript_spec.rb +0 -82
  208. data/spec/hamlit/filters/less_spec.rb +0 -37
  209. data/spec/hamlit/filters/markdown_spec.rb +0 -30
  210. data/spec/hamlit/filters/plain_spec.rb +0 -15
  211. data/spec/hamlit/filters/ruby_spec.rb +0 -24
  212. data/spec/hamlit/filters/sass_spec.rb +0 -33
  213. data/spec/hamlit/filters/scss_spec.rb +0 -37
  214. data/spec/hamlit/haml_spec.rb +0 -910
  215. data/spec/rails/.gitignore +0 -18
  216. data/spec/rails/.rspec +0 -2
  217. data/spec/rails/Gemfile +0 -19
  218. data/spec/rails/README.rdoc +0 -28
  219. data/spec/rails/Rakefile +0 -6
  220. data/spec/rails/app/assets/images/.keep +0 -0
  221. data/spec/rails/app/assets/javascripts/application.js +0 -15
  222. data/spec/rails/app/assets/stylesheets/application.css +0 -15
  223. data/spec/rails/app/controllers/application_controller.rb +0 -8
  224. data/spec/rails/app/controllers/concerns/.keep +0 -0
  225. data/spec/rails/app/controllers/users_controller.rb +0 -23
  226. data/spec/rails/app/helpers/application_helper.rb +0 -2
  227. data/spec/rails/app/mailers/.keep +0 -0
  228. data/spec/rails/app/models/.keep +0 -0
  229. data/spec/rails/app/models/concerns/.keep +0 -0
  230. data/spec/rails/app/views/application/index.html.haml +0 -18
  231. data/spec/rails/app/views/layouts/application.html.haml +0 -12
  232. data/spec/rails/app/views/users/capture.html.haml +0 -5
  233. data/spec/rails/app/views/users/capture_haml.html.haml +0 -5
  234. data/spec/rails/app/views/users/form.html.haml +0 -2
  235. data/spec/rails/app/views/users/helpers.html.haml +0 -10
  236. data/spec/rails/app/views/users/index.html.haml +0 -9
  237. data/spec/rails/app/views/users/inline.html.haml +0 -6
  238. data/spec/rails/app/views/users/old_attributes.html.haml +0 -5
  239. data/spec/rails/app/views/users/safe_buffer.html.haml +0 -4
  240. data/spec/rails/app/views/users/whitespace.html.haml +0 -4
  241. data/spec/rails/bin/bundle +0 -3
  242. data/spec/rails/bin/rails +0 -8
  243. data/spec/rails/bin/rake +0 -8
  244. data/spec/rails/bin/setup +0 -29
  245. data/spec/rails/bin/spring +0 -15
  246. data/spec/rails/config.ru +0 -4
  247. data/spec/rails/config/application.rb +0 -34
  248. data/spec/rails/config/boot.rb +0 -3
  249. data/spec/rails/config/database.yml +0 -25
  250. data/spec/rails/config/environment.rb +0 -5
  251. data/spec/rails/config/environments/development.rb +0 -41
  252. data/spec/rails/config/environments/production.rb +0 -79
  253. data/spec/rails/config/environments/test.rb +0 -42
  254. data/spec/rails/config/initializers/assets.rb +0 -11
  255. data/spec/rails/config/initializers/backtrace_silencers.rb +0 -7
  256. data/spec/rails/config/initializers/cookies_serializer.rb +0 -3
  257. data/spec/rails/config/initializers/filter_parameter_logging.rb +0 -4
  258. data/spec/rails/config/initializers/inflections.rb +0 -16
  259. data/spec/rails/config/initializers/mime_types.rb +0 -4
  260. data/spec/rails/config/initializers/session_store.rb +0 -3
  261. data/spec/rails/config/initializers/wrap_parameters.rb +0 -14
  262. data/spec/rails/config/locales/en.yml +0 -24
  263. data/spec/rails/config/routes.rb +0 -16
  264. data/spec/rails/config/secrets.yml +0 -22
  265. data/spec/rails/db/schema.rb +0 -16
  266. data/spec/rails/db/seeds.rb +0 -7
  267. data/spec/rails/lib/assets/.keep +0 -0
  268. data/spec/rails/lib/tasks/.keep +0 -0
  269. data/spec/rails/log/.keep +0 -0
  270. data/spec/rails/public/404.html +0 -67
  271. data/spec/rails/public/422.html +0 -67
  272. data/spec/rails/public/500.html +0 -66
  273. data/spec/rails/public/favicon.ico +0 -0
  274. data/spec/rails/public/robots.txt +0 -5
  275. data/spec/rails/spec/hamlit_spec.rb +0 -123
  276. data/spec/rails/spec/rails_helper.rb +0 -56
  277. data/spec/rails/spec/spec_helper.rb +0 -91
  278. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  279. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  280. data/spec/spec_helper.rb +0 -36
  281. data/spec/spec_helper/document_generator.rb +0 -93
  282. data/spec/spec_helper/render_helper.rb +0 -120
  283. 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&#x000A;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