fast_haml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +25 -0
  6. data/Appraisals +26 -0
  7. data/CHANGELOG.md +2 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +96 -0
  11. data/Rakefile +11 -0
  12. data/benchmark/rendering.rb +29 -0
  13. data/bin/fast_haml +4 -0
  14. data/ext/attribute_builder/attribute_builder.c +259 -0
  15. data/ext/attribute_builder/extconf.rb +3 -0
  16. data/fast_haml.gemspec +35 -0
  17. data/gemfiles/rails_4.0.gemfile +9 -0
  18. data/gemfiles/rails_4.1.gemfile +9 -0
  19. data/gemfiles/rails_4.2.gemfile +9 -0
  20. data/gemfiles/rails_edge.gemfile +10 -0
  21. data/haml_spec_test.rb +22 -0
  22. data/lib/fast_haml/ast.rb +112 -0
  23. data/lib/fast_haml/cli.rb +38 -0
  24. data/lib/fast_haml/compiler.rb +325 -0
  25. data/lib/fast_haml/element_parser.rb +288 -0
  26. data/lib/fast_haml/engine.rb +32 -0
  27. data/lib/fast_haml/filter_compilers/base.rb +29 -0
  28. data/lib/fast_haml/filter_compilers/cdata.rb +15 -0
  29. data/lib/fast_haml/filter_compilers/css.rb +15 -0
  30. data/lib/fast_haml/filter_compilers/escaped.rb +22 -0
  31. data/lib/fast_haml/filter_compilers/javascript.rb +15 -0
  32. data/lib/fast_haml/filter_compilers/plain.rb +17 -0
  33. data/lib/fast_haml/filter_compilers/preserve.rb +30 -0
  34. data/lib/fast_haml/filter_compilers/ruby.rb +13 -0
  35. data/lib/fast_haml/filter_compilers.rb +37 -0
  36. data/lib/fast_haml/filter_parser.rb +54 -0
  37. data/lib/fast_haml/html.rb +44 -0
  38. data/lib/fast_haml/indent_tracker.rb +84 -0
  39. data/lib/fast_haml/line_parser.rb +66 -0
  40. data/lib/fast_haml/parser.rb +251 -0
  41. data/lib/fast_haml/parser_utils.rb +17 -0
  42. data/lib/fast_haml/rails_handler.rb +10 -0
  43. data/lib/fast_haml/railtie.rb +8 -0
  44. data/lib/fast_haml/ruby_multiline.rb +23 -0
  45. data/lib/fast_haml/static_hash_parser.rb +113 -0
  46. data/lib/fast_haml/syntax_error.rb +10 -0
  47. data/lib/fast_haml/text_compiler.rb +69 -0
  48. data/lib/fast_haml/tilt.rb +16 -0
  49. data/lib/fast_haml/version.rb +3 -0
  50. data/lib/fast_haml.rb +10 -0
  51. data/spec/rails/Rakefile +6 -0
  52. data/spec/rails/app/assets/images/.keep +0 -0
  53. data/spec/rails/app/assets/javascripts/application.js +13 -0
  54. data/spec/rails/app/assets/stylesheets/application.css +15 -0
  55. data/spec/rails/app/controllers/application_controller.rb +5 -0
  56. data/spec/rails/app/controllers/books_controller.rb +8 -0
  57. data/spec/rails/app/controllers/concerns/.keep +0 -0
  58. data/spec/rails/app/helpers/application_helper.rb +2 -0
  59. data/spec/rails/app/mailers/.keep +0 -0
  60. data/spec/rails/app/models/.keep +0 -0
  61. data/spec/rails/app/models/book.rb +9 -0
  62. data/spec/rails/app/models/concerns/.keep +0 -0
  63. data/spec/rails/app/views/books/hello.html.haml +2 -0
  64. data/spec/rails/app/views/books/with_capture.html.haml +4 -0
  65. data/spec/rails/app/views/books/with_variables.html.haml +4 -0
  66. data/spec/rails/app/views/layouts/application.html.haml +9 -0
  67. data/spec/rails/bin/bundle +3 -0
  68. data/spec/rails/bin/rails +4 -0
  69. data/spec/rails/bin/rake +4 -0
  70. data/spec/rails/bin/setup +29 -0
  71. data/spec/rails/config/application.rb +12 -0
  72. data/spec/rails/config/boot.rb +3 -0
  73. data/spec/rails/config/database.yml +25 -0
  74. data/spec/rails/config/environment.rb +5 -0
  75. data/spec/rails/config/environments/development.rb +41 -0
  76. data/spec/rails/config/environments/production.rb +79 -0
  77. data/spec/rails/config/environments/test.rb +42 -0
  78. data/spec/rails/config/initializers/assets.rb +11 -0
  79. data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
  80. data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
  81. data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
  82. data/spec/rails/config/initializers/inflections.rb +16 -0
  83. data/spec/rails/config/initializers/mime_types.rb +4 -0
  84. data/spec/rails/config/initializers/secret_key_base.rb +6 -0
  85. data/spec/rails/config/initializers/session_store.rb +3 -0
  86. data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
  87. data/spec/rails/config/locales/en.yml +23 -0
  88. data/spec/rails/config/routes.rb +7 -0
  89. data/spec/rails/config/secrets.yml +22 -0
  90. data/spec/rails/config.ru +4 -0
  91. data/spec/rails/db/seeds.rb +7 -0
  92. data/spec/rails/lib/assets/.keep +0 -0
  93. data/spec/rails/lib/tasks/.keep +0 -0
  94. data/spec/rails/log/.keep +0 -0
  95. data/spec/rails/public/404.html +67 -0
  96. data/spec/rails/public/422.html +67 -0
  97. data/spec/rails/public/500.html +66 -0
  98. data/spec/rails/public/favicon.ico +0 -0
  99. data/spec/rails/public/robots.txt +5 -0
  100. data/spec/rails/spec/requests/fast_haml_spec.rb +41 -0
  101. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  102. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  103. data/spec/rails_helper.rb +4 -0
  104. data/spec/render/attribute_spec.rb +209 -0
  105. data/spec/render/comment_spec.rb +61 -0
  106. data/spec/render/doctype_spec.rb +62 -0
  107. data/spec/render/element_spec.rb +165 -0
  108. data/spec/render/filters/cdata_spec.rb +12 -0
  109. data/spec/render/filters/css_spec.rb +45 -0
  110. data/spec/render/filters/escaped_spec.rb +14 -0
  111. data/spec/render/filters/javascript_spec.rb +44 -0
  112. data/spec/render/filters/plain_spec.rb +24 -0
  113. data/spec/render/filters/preserve_spec.rb +25 -0
  114. data/spec/render/filters/ruby_spec.rb +13 -0
  115. data/spec/render/filters_spec.rb +11 -0
  116. data/spec/render/haml_comment_spec.rb +24 -0
  117. data/spec/render/multiline_spec.rb +39 -0
  118. data/spec/render/plain_spec.rb +20 -0
  119. data/spec/render/preserve_spec.rb +8 -0
  120. data/spec/render/sanitize_spec.rb +36 -0
  121. data/spec/render/script_spec.rb +74 -0
  122. data/spec/render/silent_script_spec.rb +97 -0
  123. data/spec/render/unescape_spec.rb +40 -0
  124. data/spec/spec_helper.rb +49 -0
  125. data/spec/tilt_spec.rb +33 -0
  126. metadata +427 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d18511a93b15141a266bc26d9fda1c988c6ed09a
4
+ data.tar.gz: adb2246bc9102aa0983ef003b1351e5effa3a80c
5
+ SHA512:
6
+ metadata.gz: e9ba91139b2222b4d8c4e172c8e694f6d8dc107c474402cb8e115f0d8869886a89659eb35a1b4aa84010943675ebfe235539dfd872b610a27b63caa068096d6f
7
+ data.tar.gz: 1b111f4bf03b525f4ca15201907103654a8213fd1d20d47ac8b36412af2c70acfc3b6aaae462b7a9eb37ab5c587bff9420c92693c3a3e493523532526252a3ff
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *.log
15
+
16
+ spec/rails/tmp
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "haml-spec"]
2
+ path = haml-spec
3
+ url = https://github.com/haml/haml-spec
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,25 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.0.0
5
+ - 2.1
6
+ - 2.2
7
+ - ruby-head
8
+ gemfile:
9
+ - gemfiles/rails_4.0.gemfile
10
+ - gemfiles/rails_4.1.gemfile
11
+ - gemfiles/rails_4.2.gemfile
12
+ - gemfiles/rails_edge.gemfile
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: ruby-head
16
+ - gemfile: gemfiles/rails_edge.gemfile
17
+ # https://github.com/rspec/rspec-rails/pull/1264
18
+ - rvm: 2.2
19
+ gemfile: gemfiles/rails_4.0.gemfile
20
+ exclude:
21
+ # Rails 5 requires to run on Ruby 2.2.0 or newer.
22
+ - rvm: 2.0.0
23
+ gemfile: gemfiles/rails_edge.gemfile
24
+ - rvm: 2.1
25
+ gemfile: gemfiles/rails_edge.gemfile
data/Appraisals ADDED
@@ -0,0 +1,26 @@
1
+ appraise 'rails-4.0' do
2
+ gem 'rails', '~> 4.0.0'
3
+ gem 'rspec-rails'
4
+ gem 'sqlite3'
5
+ end
6
+
7
+ appraise 'rails-4.1' do
8
+ gem 'rails', '~> 4.1.0'
9
+ gem 'rspec-rails'
10
+ gem 'sqlite3'
11
+ end
12
+
13
+ appraise 'rails-4.2' do
14
+ gem 'rails', '~> 4.2.0'
15
+ gem 'rspec-rails'
16
+ gem 'sqlite3'
17
+ end
18
+
19
+ appraise 'rails-edge' do
20
+ gem 'rails', git: 'https://github.com/rails/rails'
21
+ gem 'arel', git: 'https://github.com/rails/arel'
22
+ gem 'rspec-rails'
23
+ gem 'sqlite3'
24
+ end
25
+
26
+ # vim: set ft=ruby:
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0 (2015-02-23)
2
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fast_haml.gemspec
4
+ gemspec
5
+
6
+ gem 'rails'
7
+ gem 'rspec-rails'
8
+ gem 'sqlite3'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Kohei Suzuki
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # FastHaml
2
+ [![Build Status](https://travis-ci.org/eagletmt/fast_haml.svg)](https://travis-ci.org/eagletmt/fast_haml)
3
+ [![Coverage Status](https://coveralls.io/repos/eagletmt/fast_haml/badge.svg)](https://coveralls.io/r/eagletmt/fast_haml)
4
+ [![Code Climate](https://codeclimate.com/github/eagletmt/fast_haml/badges/gpa.svg)](https://codeclimate.com/github/eagletmt/fast_haml)
5
+
6
+ Faster implementation of Haml template language.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'fast_haml'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install fast_haml
23
+
24
+ ## Usage
25
+
26
+ Just replace your `gem 'haml'` with `gem 'fast_haml'` .
27
+
28
+ ## Incompatibilities
29
+ There are several incompatibilities.
30
+
31
+ ### Hash attributes
32
+ Hash attributes are only supported to "data" attributes.
33
+
34
+ With original haml, `%span{foo: {bar: 'baz'}}` is rendered as `<span foo-bar='baz'></span>` .
35
+ With fast_haml, it's rendered as `<span foo='{:bar=&gt;&quot;baz&quot;}'></span>` .
36
+
37
+ Only "data" attributes are converted to hyphenated attributes.
38
+
39
+ ### HTML-escape by default
40
+ Even with non-Rails project, all string are HTML-escaped.
41
+
42
+ ### "ugly" mode only
43
+ Only "ugly" mode in original haml is supported.
44
+
45
+ ```haml
46
+ %div
47
+ %div hello
48
+ ```
49
+
50
+ is always rendered as
51
+
52
+ ```html
53
+ <div>
54
+ <div>hello</div>
55
+ </div>
56
+ ```
57
+
58
+ It's equivalent to haml's "ugly" mode.
59
+
60
+ ### Others
61
+ If you find other incompatibility, please report it to me :-p.
62
+
63
+ ## Why fast_haml is faster?
64
+ ### Temple backend
65
+ I use [temple](https://github.com/judofyr/temple) to achieve faster template rendering.
66
+ It's used by [slim](https://github.com/slim-template/slim) template language & engine which is known as fast.
67
+
68
+ 1. FastHaml::Parser converts source language (Haml template) to own AST (FastHaml::Ast) .
69
+ - You can see the FastHaml::Ast by running `fast_haml parse template.haml` .
70
+ 2. FastHaml::Compiler compiles FastHaml::Ast into Temple AST.
71
+ - You can see the Temple AST by running `fast_haml temple template.haml` .
72
+ 3. Temple compiles its AST into Ruby code.
73
+ - You can see the Ruby code by running `fast_haml compile template.haml` .
74
+ - During this process, several optimizations are performed such as Temple::Filters::MultiFlattener and Temple::Filters::StaticMerger.
75
+
76
+ ### Attribute optimization
77
+ Although Haml allows arbitrary Ruby hash in the attribute syntax, most attributes are written in hash literal form.
78
+ All keys are string or symbol literals (i.e., not dynamic values) in typical case.
79
+
80
+ ```haml
81
+ %div{class: some_helper_method(current_user)}
82
+ %a{href: foo_path(@item)}= @item.name
83
+ ```
84
+
85
+ There is an optimization chance if we could know the value is String.
86
+ I introduced incompatibility to expand the chance: all attribute values are converted to String by `#to_s` except for `id`, `class` and `data` .
87
+ This will enable us to avoid runtime expensive hash merging and rendering.
88
+ The runtime hash merging is implemented by C extension in fast_haml.
89
+
90
+ ## Contributing
91
+
92
+ 1. Fork it ( https://github.com/eagletmt/fast_haml/fork )
93
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
94
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
95
+ 4. Push to the branch (`git push origin my-new-feature`)
96
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :default => [:compile, :spec]
4
+
5
+ require 'rake/extensiontask'
6
+ Rake::ExtensionTask.new('attribute_builder') do |ext|
7
+ ext.lib_dir = 'lib/fast_haml'
8
+ end
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require 'benchmark/ips'
3
+ require 'haml'
4
+ require 'fast_haml'
5
+ require 'escape_utils/html/haml'
6
+
7
+ template = ARGV[0]
8
+ unless template
9
+ $stderr.puts "Usage: #{$0} template.haml"
10
+ exit 1
11
+ end
12
+
13
+ Benchmark.ips do |x|
14
+ obj = Object.new
15
+
16
+ Haml::Engine.new(File.read(template), ugly: true, escape_html: true).def_method(obj, :haml)
17
+ code = FastHaml::Engine.new.call(File.read(template))
18
+ obj.instance_eval("def fast_haml; #{code}; end")
19
+
20
+ x.report('Haml rendering') do
21
+ obj.haml
22
+ end
23
+
24
+ x.report('FastHaml rendering') do
25
+ obj.fast_haml
26
+ end
27
+
28
+ x.compare!
29
+ end
data/bin/fast_haml ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fast_haml/cli'
3
+
4
+ FastHaml::CLI.start(ARGV)
@@ -0,0 +1,259 @@
1
+ #include <ruby.h>
2
+ #include <ruby/version.h>
3
+
4
+ #if (RUBY_API_VERSION_MAJOR > 2) || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 1)
5
+ /* define nothing */
6
+ #else
7
+ # define RARRAY_AREF(a, i) RARRAY_PTR(a)[i]
8
+ # define rb_ary_new_capa rb_ary_new2
9
+ #endif
10
+
11
+ VALUE rb_mAttributeBuilder;
12
+ static ID id_keys, id_sort_bang, id_merge_bang, id_temple, id_utils, id_escape_html, id_gsub, id_to_s;
13
+
14
+ static void
15
+ concat_array_attribute(VALUE attributes, VALUE hash, VALUE key)
16
+ {
17
+ VALUE v;
18
+
19
+ Check_Type(hash, T_HASH);
20
+ v = rb_hash_delete(hash, key);
21
+ if (!NIL_P(v)) {
22
+ VALUE ary;
23
+
24
+ v = rb_Array(v);
25
+ ary = rb_hash_lookup(attributes, key);
26
+ Check_Type(ary, T_ARRAY);
27
+ rb_ary_concat(ary, v);
28
+ }
29
+ }
30
+
31
+ static int
32
+ stringify_keys_i(VALUE key, VALUE value, VALUE arg)
33
+ {
34
+ key = rb_funcall(key, id_to_s, 0);
35
+ rb_hash_aset(arg, key, value);
36
+ return ST_CONTINUE;
37
+ }
38
+
39
+ static VALUE
40
+ stringify_keys(VALUE hash)
41
+ {
42
+ VALUE h = rb_hash_new();
43
+ rb_hash_foreach(hash, stringify_keys_i, h);
44
+ return h;
45
+ }
46
+
47
+ struct normalize_data_i2_arg {
48
+ VALUE key, normalized;
49
+ };
50
+
51
+ static int
52
+ normalize_data_i2(VALUE key, VALUE value, VALUE ptr)
53
+ {
54
+ struct normalize_data_i2_arg *arg = (struct normalize_data_i2_arg *)ptr;
55
+ VALUE k = rb_funcall(arg->key, id_to_s, 0);
56
+
57
+ k = rb_funcall(k, id_gsub, 2, rb_str_new_cstr("_"), rb_str_new_cstr("-"));
58
+ rb_str_cat(k, "-", 1);
59
+ rb_str_append(k, key);
60
+ rb_hash_aset(arg->normalized, k, value);
61
+ return ST_CONTINUE;
62
+ }
63
+
64
+ static VALUE normalize_data(VALUE data);
65
+
66
+ static int
67
+ normalize_data_i(VALUE key, VALUE value, VALUE normalized)
68
+ {
69
+ if (RB_TYPE_P(value, T_HASH)) {
70
+ struct normalize_data_i2_arg arg;
71
+ arg.key = key;
72
+ arg.normalized = normalized;
73
+ rb_hash_foreach(normalize_data(value), normalize_data_i2, (VALUE)(&arg));
74
+ } else {
75
+ key = rb_funcall(key, id_to_s, 0);
76
+ key = rb_funcall(key, id_gsub, 2, rb_str_new_cstr("_"), rb_str_new_cstr("-"));
77
+ rb_hash_aset(normalized, key, value);
78
+ }
79
+ return ST_CONTINUE;
80
+ }
81
+
82
+ static VALUE
83
+ normalize_data(VALUE data)
84
+ {
85
+ VALUE normalized;
86
+
87
+ Check_Type(data, T_HASH);
88
+ normalized = rb_hash_new();
89
+ rb_hash_foreach(data, normalize_data_i, normalized);
90
+ return normalized;
91
+ }
92
+
93
+ static void
94
+ normalize(VALUE hash)
95
+ {
96
+ VALUE keys = rb_funcall(hash, id_keys, 0);
97
+ const long len = RARRAY_LEN(keys);
98
+ long i;
99
+ for (i = 0; i < len; i++) {
100
+ VALUE key = RARRAY_AREF(keys, i);
101
+ const char *key_cstr = StringValueCStr(key);
102
+ VALUE value = rb_hash_lookup(hash, key);
103
+ if (RB_TYPE_P(value, T_HASH) && strcmp(key_cstr, "data") == 0) {
104
+ VALUE data, data_keys;
105
+ long data_len, j;
106
+
107
+ rb_hash_delete(hash, key);
108
+ data = normalize_data(value);
109
+ data_keys = rb_funcall(data, id_keys, 0);
110
+ rb_funcall(data_keys, id_sort_bang, 0);
111
+ data_len = RARRAY_LEN(data_keys);
112
+ for (j = 0; j < data_len; j++) {
113
+ VALUE data_key = RARRAY_AREF(data_keys, j);
114
+ VALUE k = rb_str_buf_new(5 + RSTRING_LEN(data_key));
115
+ rb_str_buf_cat(k, "data-", 5);
116
+ rb_str_buf_append(k, data_key);
117
+ rb_hash_aset(hash, k, rb_hash_lookup(data, data_key));
118
+ }
119
+ } else if (!RB_TYPE_P(value, T_TRUE)) {
120
+ rb_hash_aset(hash, key, rb_funcall(value, id_to_s, 0));
121
+ }
122
+ }
123
+ }
124
+
125
+ static void
126
+ merge(VALUE attributes, int argc, VALUE *argv)
127
+ {
128
+ int i;
129
+
130
+ for (i = 0; i < argc; i++) {
131
+ VALUE h;
132
+
133
+ Check_Type(argv[i], T_HASH);
134
+ h = stringify_keys(argv[i]);
135
+ concat_array_attribute(attributes, h, rb_str_new_cstr("class"));
136
+ concat_array_attribute(attributes, h, rb_str_new_cstr("id"));
137
+ normalize(h);
138
+ rb_funcall(attributes, id_merge_bang, 1, h);
139
+ }
140
+ }
141
+
142
+ static VALUE
143
+ put_attribute(VALUE attr_quote, VALUE key, VALUE value)
144
+ {
145
+ VALUE utils_class, str;
146
+ long len;
147
+
148
+ value = rb_funcall(value, id_to_s, 0);
149
+ utils_class = rb_const_get(rb_const_get(rb_cObject, id_temple), id_utils);
150
+ value = rb_funcall(utils_class, id_escape_html, 1, value);
151
+
152
+ len = 2 + 2*RSTRING_LEN(attr_quote) + RSTRING_LEN(key) + RSTRING_LEN(value);
153
+ str = rb_str_buf_new(len);
154
+ rb_str_buf_cat(str, " ", 1);
155
+ rb_str_buf_append(str, key);
156
+ rb_str_buf_cat(str, "=", 1);
157
+ rb_str_buf_append(str, attr_quote);
158
+ rb_str_buf_append(str, value);
159
+ rb_str_buf_append(str, attr_quote);
160
+ return str;
161
+ }
162
+
163
+ static VALUE
164
+ build_attribute(VALUE attr_quote, VALUE key, VALUE value)
165
+ {
166
+ const char *key_cstr = StringValueCStr(key);
167
+ if (strcmp(key_cstr, "class") == 0) {
168
+ long len;
169
+
170
+ Check_Type(value, T_ARRAY);
171
+ len = RARRAY_LEN(value);
172
+ if (len == 0) {
173
+ return rb_str_new_cstr("");
174
+ } else {
175
+ long i;
176
+ VALUE ary = rb_ary_new_capa(len);
177
+ for (i = 0; i < len; i++) {
178
+ VALUE v = RARRAY_AREF(value, i);
179
+ rb_ary_push(ary, rb_funcall(v, id_to_s, 0));
180
+ }
181
+ rb_funcall(ary, id_sort_bang, 0);
182
+ return put_attribute(attr_quote, key, rb_ary_join(ary, rb_str_new_cstr(" ")));
183
+ }
184
+ } else if (strcmp(key_cstr, "id") == 0) {
185
+ long len = RARRAY_LEN(value);
186
+
187
+ Check_Type(value, T_ARRAY);
188
+ len = RARRAY_LEN(value);
189
+ if (len == 0) {
190
+ return rb_str_new_cstr("");
191
+ } else {
192
+ long i;
193
+ VALUE ary = rb_ary_new_capa(len);
194
+ for (i = 0; i < len; i++) {
195
+ VALUE v = RARRAY_AREF(value, i);
196
+ rb_ary_push(ary, rb_funcall(v, id_to_s, 0));
197
+ }
198
+ return put_attribute(attr_quote, key, rb_ary_join(ary, rb_str_new_cstr("_")));
199
+ }
200
+ } else if (RB_TYPE_P(value, T_TRUE)) {
201
+ VALUE attr = rb_str_buf_new(1 + RSTRING_LEN(key));
202
+ rb_str_buf_cat(attr, " ", 1);
203
+ rb_str_buf_append(attr, key);
204
+ return attr;
205
+ } else {
206
+ return put_attribute(attr_quote, key, value);
207
+ }
208
+ return value;
209
+ }
210
+
211
+ static VALUE
212
+ m_build(int argc, VALUE *argv, VALUE self)
213
+ {
214
+ VALUE attr_quote, attributes, keys, buf;
215
+ long len, i;
216
+
217
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
218
+ attr_quote = argv[0];
219
+ attributes = rb_hash_new();
220
+ rb_hash_aset(attributes, rb_str_new_cstr("id"), rb_ary_new());
221
+ rb_hash_aset(attributes, rb_str_new_cstr("class"), rb_ary_new());
222
+ merge(attributes, argc-1, argv+1);
223
+
224
+ keys = rb_funcall(attributes, id_keys, 0);
225
+ rb_funcall(keys, id_sort_bang, 0);
226
+ len = RARRAY_LEN(keys);
227
+ buf = rb_ary_new_capa(len);
228
+ for (i = 0; i < len; i++) {
229
+ VALUE k = RARRAY_AREF(keys, i);
230
+ rb_ary_push(buf, build_attribute(attr_quote, k, rb_hash_lookup(attributes, k)));
231
+ }
232
+
233
+ return rb_ary_join(buf, Qnil);
234
+ }
235
+
236
+ static VALUE
237
+ m_normalize_data(VALUE self, VALUE data)
238
+ {
239
+ return normalize_data(data);
240
+ }
241
+
242
+ void
243
+ Init_attribute_builder(void)
244
+ {
245
+ VALUE mFastHaml = rb_define_module("FastHaml");
246
+ rb_mAttributeBuilder = rb_define_module_under(mFastHaml, "AttributeBuilder");
247
+ rb_define_singleton_method(rb_mAttributeBuilder, "build", RUBY_METHOD_FUNC(m_build), -1);
248
+ rb_define_singleton_method(rb_mAttributeBuilder, "normalize_data", RUBY_METHOD_FUNC(m_normalize_data), 1);
249
+
250
+ id_keys = rb_intern("keys");
251
+ id_sort_bang = rb_intern("sort!");
252
+ id_merge_bang = rb_intern("merge!");
253
+ id_temple = rb_intern("Temple");
254
+ id_utils = rb_intern("Utils");
255
+ id_escape_html = rb_intern("escape_html");
256
+ id_gsub = rb_intern("gsub");
257
+ id_to_s = rb_intern("to_s");
258
+ rb_require("temple");
259
+ }
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('fast_haml/attribute_builder')
data/fast_haml.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fast_haml/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fast_haml"
8
+ spec.version = FastHaml::VERSION
9
+ spec.authors = ["Kohei Suzuki"]
10
+ spec.email = ["eagletmt@gmail.com"]
11
+ spec.summary = %q{Faster implementation of Haml template language.}
12
+ spec.description = %q{Faster implementation of Haml template language.}
13
+ spec.homepage = "https://github.com/eagletmt/fast_haml"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.extensions = ['ext/attribute_builder/extconf.rb']
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "escape_utils"
23
+ spec.add_dependency "parser"
24
+ spec.add_dependency "temple"
25
+ spec.add_dependency "tilt"
26
+ spec.add_development_dependency "appraisal"
27
+ spec.add_development_dependency "benchmark-ips"
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "coveralls"
30
+ spec.add_development_dependency "haml"
31
+ spec.add_development_dependency "rake"
32
+ spec.add_development_dependency "rake-compiler"
33
+ spec.add_development_dependency "rspec", ">= 3"
34
+ spec.add_development_dependency "simplecov"
35
+ end
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.0.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.2.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", :git => "https://github.com/rails/rails"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+ gem "arel", :git => "https://github.com/rails/arel"
9
+
10
+ gemspec :path => "../"
data/haml_spec_test.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'minitest/autorun'
2
+ require 'json'
3
+ require 'fast_haml'
4
+
5
+ class HamlTest < Minitest::Test
6
+ contexts = JSON.parse(File.read(File.join(__dir__, 'haml-spec', 'tests.json')))
7
+ contexts.each do |context|
8
+ context[1].each do |name, test|
9
+ define_method("test_spec: #{name} (#{context[0]})") do
10
+ html = test["html"]
11
+ haml = test["haml"]
12
+ locals = Hash[(test["locals"] || {}).map {|x, y| [x.to_sym, y]}]
13
+ options = Hash[(test["config"] || {}).map {|x, y| [x.to_sym, y]}]
14
+ options[:format] = options[:format].to_sym if options.key?(:format)
15
+ tilt = Tilt.new("#{name}.haml", nil, options) { haml }
16
+ result = tilt.render(Object.new, locals)
17
+
18
+ assert_equal html, result.strip
19
+ end
20
+ end
21
+ end
22
+ end