curly-templates 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ### Unreleased
2
2
 
3
+ ### Curly 0.10.0 (July 11, 2013)
4
+
5
+ * Allow comments in Curly templates using the `{{! ... }}` syntax:
6
+
7
+ ```
8
+ {{! This is a comment }}
9
+ ```
10
+
11
+ *Daniel Schierbeck*
12
+
3
13
  ### Curly 0.9.1 (June 20, 2013)
4
14
 
5
15
  * Better error handling. If a presenter class cannot be found, we not raise a
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'curly-templates'
7
- s.version = '0.9.1'
8
- s.date = '2013-06-20'
7
+ s.version = '0.10.0'
8
+ s.date = '2013-07-11'
9
9
 
10
10
  s.summary = "Free your views!"
11
11
  s.description = "A view layer for your Rails apps that separates structure and logic."
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
41
41
  lib/curly/invalid_reference.rb
42
42
  lib/curly/presenter.rb
43
43
  lib/curly/railtie.rb
44
+ lib/curly/scanner.rb
44
45
  lib/curly/template_handler.rb
45
46
  lib/generators/curly/controller/controller_generator.rb
46
47
  lib/generators/curly/controller/templates/presenter.rb.erb
@@ -49,6 +50,7 @@ Gem::Specification.new do |s|
49
50
  spec/compiler_spec.rb
50
51
  spec/generators/controller_generator_spec.rb
51
52
  spec/presenter_spec.rb
53
+ spec/scanner_spec.rb
52
54
  spec/spec_helper.rb
53
55
  spec/template_handler_spec.rb
54
56
  ]
data/lib/curly.rb CHANGED
@@ -26,7 +26,7 @@
26
26
  # See Curly::Presenter for more information on presenters.
27
27
  #
28
28
  module Curly
29
- VERSION = "0.9.1"
29
+ VERSION = "0.10.0"
30
30
 
31
31
  # Compiles a Curly template to Ruby code.
32
32
  #
@@ -1,64 +1,92 @@
1
+ require 'curly/scanner'
1
2
  require 'curly/invalid_reference'
2
3
 
3
4
  module Curly
5
+
6
+ # Compiles Curly templates into executable Ruby code.
7
+ #
8
+ # A template must be accompanied by a presenter class. This class defines the
9
+ # references that are valid within the template.
10
+ #
4
11
  class Compiler
5
- REFERENCE_REGEX = %r(\{\{([\w\.]+)\}\})
12
+ # Compiles a Curly template to Ruby code.
13
+ #
14
+ # template - The template String that should be compiled.
15
+ # presenter_class - The presenter Class.
16
+ #
17
+ # Returns a String containing the Ruby code.
18
+ def self.compile(template, presenter_class)
19
+ new(template, presenter_class).compile
20
+ end
6
21
 
7
- class << self
22
+ # Whether the Curly template is valid. This includes whether all
23
+ # references are available on the presenter class.
24
+ #
25
+ # template - The template String that should be validated.
26
+ # presenter_class - The presenter Class.
27
+ #
28
+ # Returns true if the template is valid, false otherwise.
29
+ def self.valid?(template, presenter_class)
30
+ compile(template, presenter_class)
31
+
32
+ true
33
+ rescue InvalidReference
34
+ false
35
+ end
8
36
 
9
- # Compiles a Curly template to Ruby code.
10
- #
11
- # template - The template String that should be compiled.
12
- #
13
- # Returns a String containing the Ruby code.
14
- def compile(template, presenter_class)
15
- if presenter_class.nil?
16
- raise ArgumentError, "presenter class cannot be nil"
17
- end
37
+ attr_reader :template, :presenter_class
18
38
 
19
- source = template.inspect
20
- source.gsub!(REFERENCE_REGEX) { compile_reference($1, presenter_class) }
39
+ def initialize(template, presenter_class)
40
+ @template, @presenter_class = template, presenter_class
41
+ end
21
42
 
22
- source
43
+ def compile
44
+ if presenter_class.nil?
45
+ raise ArgumentError, "presenter class cannot be nil"
23
46
  end
24
47
 
25
- # Whether the Curly template is valid. This includes whether all
26
- # references are available on the presenter class.
27
- #
28
- # template - The template String that should be validated.
29
- # presenter_class - The presenter Class.
30
- #
31
- # Returns true if the template is valid, false otherwise.
32
- def valid?(template, presenter_class)
33
- compile(template, presenter_class)
34
-
35
- true
36
- rescue InvalidReference
37
- false
48
+ tokens = Scanner.scan(template)
49
+
50
+ parts = tokens.map do |type, value|
51
+ send("compile_#{type}", value)
38
52
  end
39
53
 
40
- private
54
+ parts.join(" << ")
55
+ end
41
56
 
42
- def compile_reference(reference, presenter_class)
43
- method, argument = reference.split(".", 2)
57
+ private
44
58
 
45
- unless presenter_class.method_available?(method.to_sym)
46
- raise Curly::InvalidReference.new(method.to_sym)
47
- end
59
+ def compile_reference(reference)
60
+ method, argument = reference.split(".", 2)
48
61
 
49
- if presenter_class.instance_method(method).arity == 1
50
- # The method accepts a single argument -- pass it in.
51
- code = <<-RUBY
52
- presenter.#{method}(#{argument.inspect}) {|*args| yield(*args) }
53
- RUBY
54
- else
55
- code = <<-RUBY
56
- presenter.#{method} {|*args| yield(*args) }
57
- RUBY
58
- end
62
+ unless presenter_class.method_available?(method.to_sym)
63
+ raise Curly::InvalidReference.new(method.to_sym)
64
+ end
59
65
 
60
- '#{ERB::Util.html_escape(%s)}' % code.strip
66
+ if presenter_class.instance_method(method).arity == 1
67
+ # The method accepts a single argument -- pass it in.
68
+ code = <<-RUBY
69
+ presenter.#{method}(#{argument.inspect}) {|*args| yield(*args) }
70
+ RUBY
71
+ else
72
+ code = <<-RUBY
73
+ presenter.#{method} {|*args| yield(*args) }
74
+ RUBY
61
75
  end
76
+
77
+ 'ERB::Util.html_escape(%s)' % code.strip
78
+ end
79
+
80
+ def compile_text(text)
81
+ text.inspect
82
+ end
83
+
84
+ def compile_comment_line(comment)
85
+ "''" # Replace the content with an empty string.
86
+ end
87
+
88
+ def compile_comment(comment)
89
+ "''" # Replace the content with an empty string.
62
90
  end
63
91
  end
64
92
  end
@@ -0,0 +1,111 @@
1
+ require 'strscan'
2
+
3
+ module Curly
4
+
5
+ # Scans Curly templates for tokens.
6
+ #
7
+ # The Scanner goes through the template piece by piece, extracting tokens
8
+ # until the end of the template is reached.
9
+ #
10
+ class Scanner
11
+ REFERENCE_REGEX = %r(\{\{[\w\.]+\}\})
12
+ COMMENT_REGEX = %r(\{\{!.*\}\})
13
+ COMMENT_LINE_REGEX = %r(\s*#{COMMENT_REGEX}\s*\n)
14
+
15
+ # Scans a Curly template for tokens.
16
+ #
17
+ # source - The String source of the template.
18
+ #
19
+ # Example
20
+ #
21
+ # Curly::Scanner.scan("hello {{name}}!")
22
+ # #=> [[:text, "hello "], [:reference, "name"], [:text, "!"]]
23
+ #
24
+ # Returns an Array of type/value pairs representing the tokens in the
25
+ # template.
26
+ def self.scan(source)
27
+ new(source).scan
28
+ end
29
+
30
+ def initialize(source)
31
+ @scanner = StringScanner.new(source)
32
+ end
33
+
34
+ def scan
35
+ tokens = []
36
+ tokens << scan_token until @scanner.eos?
37
+ tokens
38
+ end
39
+
40
+ private
41
+
42
+ # Scans the next token in the template.
43
+ #
44
+ # Returns a two-element Array, the first element being the Symbol type of
45
+ # the token and the second being the String value.
46
+ def scan_token
47
+ scan_reference ||
48
+ scan_comment_line ||
49
+ scan_comment ||
50
+ scan_text ||
51
+ scan_remainder
52
+ end
53
+
54
+ # Scans a reference token, if a reference is the next token in the template.
55
+ #
56
+ # Returns an Array representing the token, or nil if no reference token can
57
+ # be found at the current position.
58
+ def scan_reference
59
+ if value = @scanner.scan(REFERENCE_REGEX)
60
+ # Return the reference name excluding "{{" and "}}".
61
+ [:reference, value[2..-3]]
62
+ end
63
+ end
64
+
65
+ # Scans a comment line token, if a comment line is the next token in the
66
+ # template.
67
+ #
68
+ # Returns an Array representing the token, or nil if no comment line token
69
+ # can be found at the current position.
70
+ def scan_comment_line
71
+ if value = @scanner.scan(COMMENT_LINE_REGEX)
72
+ # Returns the comment excluding "{{!" and "}}".
73
+ [:comment_line, value[3..-4]]
74
+ end
75
+ end
76
+
77
+ # Scans a comment token, if a comment is the next token in the template.
78
+ #
79
+ # Returns an Array representing the token, or nil if no comment token can
80
+ # be found at the current position.
81
+ def scan_comment
82
+ if value = @scanner.scan(COMMENT_REGEX)
83
+ # Returns the comment excluding "{{!" and "}}".
84
+ [:comment, value[3..-3]]
85
+ end
86
+ end
87
+
88
+ # Scans a text token, if a text is the next token in the template.
89
+ #
90
+ # Returns an Array representing the token, or nil if no text token can
91
+ # be found at the current position.
92
+ def scan_text
93
+ if value = @scanner.scan_until(/\{\{/)
94
+ # Rewind the scanner until before the "{{"
95
+ @scanner.pos -= 2
96
+
97
+ # Return the text up until "{{".
98
+ [:text, value[0..-3]]
99
+ end
100
+ end
101
+
102
+ # Scans the remainder of the template and treats it as a text token.
103
+ #
104
+ # Returns an Array representing the token, or nil if no text is remaining.
105
+ def scan_remainder
106
+ if value = @scanner.scan(/.+/m)
107
+ [:text, value]
108
+ end
109
+ end
110
+ end
111
+ end
@@ -94,6 +94,22 @@ describe Curly::Compiler do
94
94
  presenter.stub(:dirty) { "<p>dirty</p>".html_safe }
95
95
  evaluate("{{dirty}}").should == "<p>dirty</p>"
96
96
  end
97
+
98
+ it "removes comments from the output" do
99
+ evaluate("HELO{{! I'm a comment, yo }}WORLD").should == "HELOWORLD"
100
+ end
101
+
102
+ it "removes comment lines from the output" do
103
+ evaluate(<<-CURLY.strip_heredoc).should == "HELO\nWORLD\n"
104
+ HELO
105
+ {{! I'm a comment }}
106
+ WORLD
107
+ CURLY
108
+ end
109
+
110
+ it "does not execute arbitrary Ruby code" do
111
+ evaluate('#{foo}').should == '#{foo}'
112
+ end
97
113
  end
98
114
 
99
115
  describe ".valid?" do
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Curly::Scanner, ".scan" do
4
+ it "returns the tokens in the source" do
5
+ scan("foo {{bar}} baz").should == [
6
+ [:text, "foo "],
7
+ [:reference, "bar"],
8
+ [:text, " baz"]
9
+ ]
10
+ end
11
+
12
+ it "scans parameterized references" do
13
+ scan("{{foo.bar}}").should == [
14
+ [:reference, "foo.bar"]
15
+ ]
16
+ end
17
+
18
+ it "scans comments in the source" do
19
+ scan("foo {{!bar}} baz").should == [
20
+ [:text, "foo "],
21
+ [:comment, "bar"],
22
+ [:text, " baz"]
23
+ ]
24
+ end
25
+
26
+ it "scans comment lines in the source" do
27
+ scan("foo\n{{!bar}}\nbaz").should == [
28
+ [:text, "foo\n"],
29
+ [:comment_line, "bar"],
30
+ [:text, "baz"]
31
+ ]
32
+ end
33
+
34
+ it "scans to the end of the source" do
35
+ scan("foo\n").should == [
36
+ [:text, "foo\n"]
37
+ ]
38
+ end
39
+
40
+ it "treats quotes as text" do
41
+ scan('"').should == [
42
+ [:text, '"']
43
+ ]
44
+ end
45
+
46
+ it "treats Ruby interpolation as text" do
47
+ scan('#{foo}').should == [
48
+ [:text, '#{foo}']
49
+ ]
50
+ end
51
+
52
+ def scan(source)
53
+ Curly::Scanner.scan(source)
54
+ end
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curly-templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-20 00:00:00.000000000 Z
12
+ date: 2013-07-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -122,6 +122,7 @@ files:
122
122
  - lib/curly/invalid_reference.rb
123
123
  - lib/curly/presenter.rb
124
124
  - lib/curly/railtie.rb
125
+ - lib/curly/scanner.rb
125
126
  - lib/curly/template_handler.rb
126
127
  - lib/generators/curly/controller/controller_generator.rb
127
128
  - lib/generators/curly/controller/templates/presenter.rb.erb
@@ -130,6 +131,7 @@ files:
130
131
  - spec/compiler_spec.rb
131
132
  - spec/generators/controller_generator_spec.rb
132
133
  - spec/presenter_spec.rb
134
+ - spec/scanner_spec.rb
133
135
  - spec/spec_helper.rb
134
136
  - spec/template_handler_spec.rb
135
137
  homepage: https://github.com/zendesk/curly
@@ -148,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
148
150
  version: '0'
149
151
  segments:
150
152
  - 0
151
- hash: 3388653685709819519
153
+ hash: 4015061898851795676
152
154
  required_rubygems_version: !ruby/object:Gem::Requirement
153
155
  none: false
154
156
  requirements:
@@ -165,5 +167,6 @@ test_files:
165
167
  - spec/compiler_spec.rb
166
168
  - spec/generators/controller_generator_spec.rb
167
169
  - spec/presenter_spec.rb
170
+ - spec/scanner_spec.rb
168
171
  - spec/template_handler_spec.rb
169
172
  has_rdoc: