curly-templates 0.9.1 → 0.10.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.
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: