faml 0.2.0

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.
Files changed (140) 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 +27 -0
  6. data/Appraisals +26 -0
  7. data/CHANGELOG.md +47 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +115 -0
  11. data/Rakefile +28 -0
  12. data/benchmark/attribute_builder.haml +5 -0
  13. data/benchmark/rendering.rb +35 -0
  14. data/bin/faml +4 -0
  15. data/ext/attribute_builder/attribute_builder.c +261 -0
  16. data/ext/attribute_builder/extconf.rb +3 -0
  17. data/faml.gemspec +38 -0
  18. data/gemfiles/rails_4.0.gemfile +9 -0
  19. data/gemfiles/rails_4.1.gemfile +9 -0
  20. data/gemfiles/rails_4.2.gemfile +9 -0
  21. data/gemfiles/rails_edge.gemfile +10 -0
  22. data/haml_spec_test.rb +22 -0
  23. data/lib/faml.rb +10 -0
  24. data/lib/faml/ast.rb +112 -0
  25. data/lib/faml/cli.rb +38 -0
  26. data/lib/faml/compiler.rb +374 -0
  27. data/lib/faml/element_parser.rb +235 -0
  28. data/lib/faml/engine.rb +34 -0
  29. data/lib/faml/filter_compilers.rb +41 -0
  30. data/lib/faml/filter_compilers/base.rb +43 -0
  31. data/lib/faml/filter_compilers/cdata.rb +15 -0
  32. data/lib/faml/filter_compilers/coffee.rb +16 -0
  33. data/lib/faml/filter_compilers/css.rb +16 -0
  34. data/lib/faml/filter_compilers/escaped.rb +23 -0
  35. data/lib/faml/filter_compilers/javascript.rb +16 -0
  36. data/lib/faml/filter_compilers/markdown.rb +18 -0
  37. data/lib/faml/filter_compilers/plain.rb +15 -0
  38. data/lib/faml/filter_compilers/preserve.rb +26 -0
  39. data/lib/faml/filter_compilers/ruby.rb +17 -0
  40. data/lib/faml/filter_compilers/sass.rb +15 -0
  41. data/lib/faml/filter_compilers/scss.rb +16 -0
  42. data/lib/faml/filter_compilers/tilt_base.rb +34 -0
  43. data/lib/faml/filter_parser.rb +54 -0
  44. data/lib/faml/html.rb +58 -0
  45. data/lib/faml/indent_tracker.rb +84 -0
  46. data/lib/faml/line_parser.rb +66 -0
  47. data/lib/faml/newline.rb +30 -0
  48. data/lib/faml/parser.rb +211 -0
  49. data/lib/faml/parser_utils.rb +17 -0
  50. data/lib/faml/rails_handler.rb +10 -0
  51. data/lib/faml/railtie.rb +9 -0
  52. data/lib/faml/ruby_multiline.rb +23 -0
  53. data/lib/faml/script_parser.rb +84 -0
  54. data/lib/faml/static_hash_parser.rb +113 -0
  55. data/lib/faml/syntax_error.rb +10 -0
  56. data/lib/faml/text_compiler.rb +69 -0
  57. data/lib/faml/tilt.rb +17 -0
  58. data/lib/faml/version.rb +3 -0
  59. data/spec/compiler_newline_spec.rb +162 -0
  60. data/spec/rails/Rakefile +6 -0
  61. data/spec/rails/app/assets/images/.keep +0 -0
  62. data/spec/rails/app/assets/javascripts/application.js +13 -0
  63. data/spec/rails/app/assets/stylesheets/application.css +15 -0
  64. data/spec/rails/app/controllers/application_controller.rb +5 -0
  65. data/spec/rails/app/controllers/books_controller.rb +8 -0
  66. data/spec/rails/app/controllers/concerns/.keep +0 -0
  67. data/spec/rails/app/helpers/application_helper.rb +2 -0
  68. data/spec/rails/app/mailers/.keep +0 -0
  69. data/spec/rails/app/models/.keep +0 -0
  70. data/spec/rails/app/models/book.rb +9 -0
  71. data/spec/rails/app/models/concerns/.keep +0 -0
  72. data/spec/rails/app/views/books/hello.html.haml +2 -0
  73. data/spec/rails/app/views/books/with_capture.html.haml +4 -0
  74. data/spec/rails/app/views/books/with_variables.html.haml +4 -0
  75. data/spec/rails/app/views/layouts/application.html.haml +9 -0
  76. data/spec/rails/bin/bundle +3 -0
  77. data/spec/rails/bin/rails +4 -0
  78. data/spec/rails/bin/rake +4 -0
  79. data/spec/rails/bin/setup +29 -0
  80. data/spec/rails/config.ru +4 -0
  81. data/spec/rails/config/application.rb +12 -0
  82. data/spec/rails/config/boot.rb +3 -0
  83. data/spec/rails/config/database.yml +25 -0
  84. data/spec/rails/config/environment.rb +5 -0
  85. data/spec/rails/config/environments/development.rb +41 -0
  86. data/spec/rails/config/environments/production.rb +79 -0
  87. data/spec/rails/config/environments/test.rb +42 -0
  88. data/spec/rails/config/initializers/assets.rb +11 -0
  89. data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
  90. data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
  91. data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
  92. data/spec/rails/config/initializers/inflections.rb +16 -0
  93. data/spec/rails/config/initializers/mime_types.rb +4 -0
  94. data/spec/rails/config/initializers/secret_key_base.rb +6 -0
  95. data/spec/rails/config/initializers/session_store.rb +3 -0
  96. data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
  97. data/spec/rails/config/locales/en.yml +23 -0
  98. data/spec/rails/config/routes.rb +7 -0
  99. data/spec/rails/config/secrets.yml +22 -0
  100. data/spec/rails/db/seeds.rb +7 -0
  101. data/spec/rails/lib/assets/.keep +0 -0
  102. data/spec/rails/lib/tasks/.keep +0 -0
  103. data/spec/rails/log/.keep +0 -0
  104. data/spec/rails/public/404.html +67 -0
  105. data/spec/rails/public/422.html +67 -0
  106. data/spec/rails/public/500.html +66 -0
  107. data/spec/rails/public/favicon.ico +0 -0
  108. data/spec/rails/public/robots.txt +5 -0
  109. data/spec/rails/spec/requests/faml_spec.rb +41 -0
  110. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  111. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  112. data/spec/rails_helper.rb +4 -0
  113. data/spec/render/attribute_spec.rb +241 -0
  114. data/spec/render/comment_spec.rb +61 -0
  115. data/spec/render/doctype_spec.rb +57 -0
  116. data/spec/render/element_spec.rb +136 -0
  117. data/spec/render/filters/cdata_spec.rb +12 -0
  118. data/spec/render/filters/coffee_spec.rb +25 -0
  119. data/spec/render/filters/css_spec.rb +45 -0
  120. data/spec/render/filters/escaped_spec.rb +14 -0
  121. data/spec/render/filters/javascript_spec.rb +44 -0
  122. data/spec/render/filters/markdown_spec.rb +19 -0
  123. data/spec/render/filters/plain_spec.rb +24 -0
  124. data/spec/render/filters/preserve_spec.rb +24 -0
  125. data/spec/render/filters/ruby_spec.rb +13 -0
  126. data/spec/render/filters/sass_spec.rb +28 -0
  127. data/spec/render/filters/scss_spec.rb +32 -0
  128. data/spec/render/filters_spec.rb +11 -0
  129. data/spec/render/haml_comment_spec.rb +24 -0
  130. data/spec/render/multiline_spec.rb +39 -0
  131. data/spec/render/newline_spec.rb +83 -0
  132. data/spec/render/plain_spec.rb +20 -0
  133. data/spec/render/preserve_spec.rb +8 -0
  134. data/spec/render/sanitize_spec.rb +36 -0
  135. data/spec/render/script_spec.rb +81 -0
  136. data/spec/render/silent_script_spec.rb +97 -0
  137. data/spec/render/unescape_spec.rb +45 -0
  138. data/spec/spec_helper.rb +47 -0
  139. data/spec/tilt_spec.rb +33 -0
  140. metadata +489 -0
@@ -0,0 +1,18 @@
1
+ require 'faml/filter_compilers/tilt_base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Markdown < TiltBase
6
+ def need_newline?
7
+ false
8
+ end
9
+
10
+ def compile(texts)
11
+ temple = [:multi, [:newline]]
12
+ compile_with_tilt(temple, 'markdown', texts)
13
+ end
14
+ end
15
+
16
+ register(:markdown, Markdown)
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'faml/filter_compilers/base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Plain < Base
6
+ def compile(texts)
7
+ temple = [:multi, [:newline]]
8
+ compile_texts(temple, texts)
9
+ temple
10
+ end
11
+ end
12
+
13
+ register(:plain, Plain)
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require 'faml/filter_compilers/base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Preserve < Base
6
+ include Temple::Utils
7
+
8
+ def compile(texts)
9
+ temple = [:multi, [:newline]]
10
+ # I don't know why only :preserve filter keeps the last empty lines.
11
+ compile_texts(temple, texts, keep_last_empty_lines: true)
12
+ sym = unique_name
13
+ [:multi,
14
+ [:capture, sym, temple],
15
+ [:dynamic, "::Faml::FilterCompilers::Preserve.preserve(#{sym})"],
16
+ ]
17
+ end
18
+
19
+ def self.preserve(str)
20
+ str.gsub("\n", '&#x000A;')
21
+ end
22
+ end
23
+
24
+ register(:preserve, Preserve)
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'faml/filter_compilers/base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Ruby < Base
6
+ def need_newline?
7
+ false
8
+ end
9
+
10
+ def compile(texts)
11
+ [:multi, [:newline], [:code, strip_last_empty_lines(texts).join("\n")]]
12
+ end
13
+ end
14
+
15
+ register(:ruby, Ruby)
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'faml/filter_compilers/tilt_base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Sass < TiltBase
6
+ def compile(texts)
7
+ temple = [:multi, [:static, "\n"], [:newline]]
8
+ compile_with_tilt(temple, 'sass', texts)
9
+ [:haml, :tag, 'style', false, [:html, :attrs], temple]
10
+ end
11
+ end
12
+
13
+ register(:sass, Sass)
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'faml/filter_compilers/tilt_base'
2
+
3
+ module Faml
4
+ module FilterCompilers
5
+ class Scss < TiltBase
6
+ def compile(texts)
7
+ temple = [:multi, [:static, "\n"], [:newline]]
8
+ compile_with_tilt(temple, 'scss', texts)
9
+ temple << [:static, "\n"]
10
+ [:haml, :tag, 'style', false, [:html, :attrs], temple]
11
+ end
12
+ end
13
+
14
+ register(:scss, Scss)
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ require 'temple'
2
+ require 'tilt'
3
+ require 'faml/filter_compilers/base'
4
+ require 'faml/text_compiler'
5
+
6
+ module Faml
7
+ module FilterCompilers
8
+ class TiltBase < Base
9
+ include Temple::Utils
10
+
11
+ def self.render_with_tilt(name, source)
12
+ ::Tilt["t.#{name}"].new { source }.render
13
+ end
14
+
15
+ protected
16
+
17
+ def compile_with_tilt(temple, name, texts)
18
+ source = texts.join("\n")
19
+ if TextCompiler.contains_interpolation?(source)
20
+ text_temple = [:multi]
21
+ compile_texts(text_temple, texts)
22
+ sym = unique_name
23
+ temple << [:capture, sym, text_temple]
24
+ temple << [:dynamic, "::Faml::FilterCompilers::TiltBase.render_with_tilt(#{name.inspect}, #{sym})"]
25
+ else
26
+ compiled = self.class.render_with_tilt(name, source)
27
+ temple << [:static, compiled]
28
+ temple.concat([[:newline]] * (texts.size - 1))
29
+ end
30
+ temple
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ module Faml
2
+ class FilterParser
3
+ def initialize(indent_tracker)
4
+ @ast = nil
5
+ @indent_level = nil
6
+ @indent_tracker = indent_tracker
7
+ end
8
+
9
+ def enabled?
10
+ !!@ast
11
+ end
12
+
13
+ def start(name)
14
+ @ast = Ast::Filter.new
15
+ @ast.name = name
16
+ end
17
+
18
+ def append(line)
19
+ indent, text = @indent_tracker.split(line)
20
+ if text.empty?
21
+ @ast.texts << ''
22
+ return
23
+ end
24
+ indent_level = indent.size
25
+
26
+ if @indent_level
27
+ if indent_level < @indent_level
28
+ # Finish filter
29
+ @indent_level = nil
30
+ ast = @ast
31
+ @ast = nil
32
+ return ast
33
+ end
34
+ else
35
+ if indent_level > @indent_tracker.current_level
36
+ # Start filter
37
+ @indent_level = indent_level
38
+ else
39
+ # Empty filter
40
+ @ast = nil
41
+ return nil
42
+ end
43
+ end
44
+
45
+ text = line[@indent_level .. -1]
46
+ @ast.texts << text
47
+ nil
48
+ end
49
+
50
+ def finish
51
+ @ast
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ require 'faml/attribute_builder'
2
+
3
+ module Faml
4
+ class Html < Temple::HTML::Fast
5
+ def on_haml_tag(name, self_closing, attrs, content = nil)
6
+ name = name.to_s
7
+ closed = self_closing && (!content || empty_exp?(content))
8
+ result = [:multi, [:static, "<#{name}"], compile(attrs)]
9
+ result << [:static, (closed && @format != :html ? ' /' : '') + '>']
10
+ result << compile(content) if content
11
+ result << [:static, "</#{name}>"] if !closed
12
+ result
13
+ end
14
+
15
+ def on_haml_attrs(code)
16
+ [:dynamic, "::Faml::AttributeBuilder.build(#{options[:attr_quote].inspect}, #{code})"]
17
+ end
18
+
19
+ def on_haml_attr(name, value)
20
+ if empty_exp?(value)
21
+ if @format == :html
22
+ [:static, " #{name}"]
23
+ else
24
+ [:static, " #{name}=#{options[:attr_quote]}#{name}#{options[:attr_quote]}"]
25
+ end
26
+ elsif value[0] == :dvalue
27
+ sym = unique_name
28
+ [:multi,
29
+ [:code, "#{sym} = (#{value[1]})"],
30
+ [:case, sym,
31
+ ['true', [:static, " #{name}"]],
32
+ ['false, nil', [:multi]],
33
+ [:else, [:multi,
34
+ [:static, " #{name}=#{options[:attr_quote]}"],
35
+ [:escape, true, [:dynamic, sym]],
36
+ [:static, options[:attr_quote]],
37
+ ]],
38
+ ],
39
+ ]
40
+ else
41
+ [:multi,
42
+ [:static, " #{name}=#{options[:attr_quote]}"],
43
+ compile(value),
44
+ [:static, options[:attr_quote]]]
45
+ end
46
+ end
47
+
48
+ def on_haml_doctype(type)
49
+ compile([:html, :doctype, type])
50
+ rescue Temple::FilterError
51
+ [:multi]
52
+ end
53
+
54
+ def on_haml_preserve(sym)
55
+ [:dynamic, "::Faml::Compiler.find_and_preserve(#{sym}.to_s)"]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,84 @@
1
+ module Faml
2
+ class IndentTracker
3
+ class IndentMismatch < StandardError
4
+ attr_reader :lineno
5
+
6
+ def initialize(message, lineno)
7
+ super("#{message} at line #{lineno}")
8
+ @lineno = lineno
9
+ end
10
+ end
11
+
12
+ def initialize(on_enter: nil, on_leave: nil)
13
+ @indent_levels = [0]
14
+ @on_enter = on_enter || lambda { |level, text| }
15
+ @on_leave = on_leave || lambda { |level, text| }
16
+ @comment_level = nil
17
+ end
18
+
19
+ def process(line, lineno)
20
+ indent, text = split(line)
21
+ indent_level = indent.size
22
+
23
+ unless text.empty?
24
+ track(indent_level, text, lineno)
25
+ end
26
+ [text, indent]
27
+ end
28
+
29
+ def split(line)
30
+ m = line.match(/\A( *)(.*)\z/)
31
+ [m[1], m[2]]
32
+ end
33
+
34
+ def finish
35
+ indent_leave(0, '', -1)
36
+ end
37
+
38
+ def current_level
39
+ @indent_levels.last
40
+ end
41
+
42
+ def enter_comment!
43
+ @comment_level = @indent_levels[-2]
44
+ end
45
+
46
+ private
47
+
48
+ def track(indent_level, text, lineno)
49
+ if indent_level > @indent_levels.last
50
+ indent_enter(indent_level, text)
51
+ elsif indent_level < @indent_levels.last
52
+ indent_leave(indent_level, text, lineno)
53
+ end
54
+ end
55
+
56
+ def indent_enter(indent_level, text)
57
+ unless @comment_level
58
+ @indent_levels.push(indent_level)
59
+ @on_enter.call(indent_level, text)
60
+ end
61
+ end
62
+
63
+ def indent_leave(indent_level, text, lineno)
64
+ if @comment_level
65
+ if indent_level <= @comment_level
66
+ # finish comment mode
67
+ @comment_level = nil
68
+ else
69
+ # still in comment
70
+ return
71
+ end
72
+ end
73
+
74
+ while indent_level < @indent_levels.last
75
+ @indent_levels.pop
76
+ @on_leave.call(indent_level, text)
77
+ end
78
+
79
+ if indent_level != @indent_levels.last
80
+ raise IndentMismatch.new("Unexpected indent level: #{indent_level}: indent_level=#{@indent_levels}", lineno)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,66 @@
1
+ module Faml
2
+ class LineParser
3
+ attr_reader :lineno
4
+
5
+ def initialize(template_str)
6
+ @lines = template_str.each_line.map { |line| line.chomp.rstrip }
7
+ @lineno = 0
8
+ end
9
+
10
+ def next_line
11
+ line = move_next
12
+ if is_multiline?(line)
13
+ next_multiline(line)
14
+ else
15
+ line
16
+ end
17
+ end
18
+
19
+ def has_next?
20
+ @lineno < @lines.size
21
+ end
22
+
23
+ private
24
+
25
+ MULTILINE_SUFFIX = ' |'
26
+
27
+ # Regex to check for blocks with spaces around arguments. Not to be confused
28
+ # with multiline script.
29
+ # For example:
30
+ # foo.each do | bar |
31
+ # = bar
32
+ #
33
+ BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/o
34
+
35
+ def is_multiline?(line)
36
+ line = line.lstrip
37
+ line.end_with?(MULTILINE_SUFFIX) && line !~ BLOCK_WITH_SPACES
38
+ end
39
+
40
+ def move_next
41
+ @lines[@lineno].tap do
42
+ @lineno += 1
43
+ end
44
+ end
45
+
46
+ def move_back
47
+ @lineno -= 1
48
+ end
49
+
50
+ def next_multiline(line)
51
+ buf = [line[0, line.size-1]]
52
+ while @lineno < @lines.size
53
+ line = move_next
54
+
55
+ if is_multiline?(line)
56
+ line = line[0, line.size-1]
57
+ buf << line.lstrip
58
+ else
59
+ move_back
60
+ break
61
+ end
62
+ end
63
+ buf.join
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ require 'temple'
2
+
3
+ module Faml
4
+ class Newline < Temple::Filter
5
+ def on_multi(*exprs)
6
+ i = exprs.size-1
7
+ marker = false
8
+ while i >= 0
9
+ case exprs[i]
10
+ when [:rmnl]
11
+ if marker
12
+ raise "InternalError: double rmnl error"
13
+ else
14
+ marker = true
15
+ exprs.delete_at(i)
16
+ end
17
+ when [:mknl]
18
+ if marker
19
+ marker = false
20
+ exprs.delete_at(i)
21
+ else
22
+ exprs[i] = [:static, "\n"]
23
+ end
24
+ end
25
+ i -= 1
26
+ end
27
+ [:multi, *exprs]
28
+ end
29
+ end
30
+ end