curly-templates 0.12.0 → 1.0.0rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a564172d0d4f15b3bae6c5f05579d5deb0fe845b
4
- data.tar.gz: d28d0cf8d4998251a08d88fb13d3e44250a61a5c
3
+ metadata.gz: c1d51d31a9e02cb1bccbdac92afeee3faa5ae093
4
+ data.tar.gz: 08eb307eecf7be51a180cc11a257b3ad460d6fa6
5
5
  SHA512:
6
- metadata.gz: 6b360305771e280435d7ad2cb0fd07cd982728223e83701af89411037c1f61926f02e0594aee6c7e696da369dae600dcd3aceb437fc82178d2250897bf8a8a67
7
- data.tar.gz: 8a16c5647e0059ec808fc48e92a4f843530d4036b7a744cc6aef3f19d52ae149f6d20e27c327f215b4cb2df40522b32d97a1b88d7559f34c84fff6ad9a7f7fdd
6
+ metadata.gz: 62a6e70667b692c94e5b48dae4dffa84d49f096a8a34104dc827e4a5087f53df8a870b684cf106c606e6b208067ec233cf35ada8f9dfa580910ec1d55278472b
7
+ data.tar.gz: a07ef3ed26980e8751287a9a00770f32badd4c6fa3e1e50b16be83d23ae0cd3f17cbfd910add61050d780cee9e88abbf03a308c75d6c0d11058a3981d34173a4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ### Unreleased
2
2
 
3
+ ### Curly 0.12.0 (December 3, 2013)
4
+
3
5
  * Allow Curly to output Curly syntax by using the `{{{ ... }}` syntax:
4
6
 
5
7
  ```
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,17 @@
1
+ In order to keep the Curly code base nice and tidy, please observe these best practises when making contributions:
2
+
3
+ - Add tests for all your code. Make sure the tests are clear and fail with proper error messages. It's a good idea to let your test fail in order to review whether the message makes sense; then make the test pass.
4
+ - Document any unclear things in the code. Even better, don't make the code do unclear things.
5
+ - Use the coding style already present in the code base.
6
+ - Make your commit messages precise and to the point. Add a short summary (50 chars max) followed by a blank line and then a longer description, if necessary, e.g.
7
+
8
+ > Make invalid references raise an exception
9
+ >
10
+ > In order to avoid nasty errors when doing stuff, make the Curly compiler
11
+ > fail early when an invalid reference is encountered.
12
+
13
+
14
+ Before making a contribution, you should make sure to understand what Curly is and isn't:
15
+
16
+ - The template language will never be super advanced: one of the primary use cases for Curly is to allow end users to mess around with Curly templates and have them safely compiled and rendered on a server. As such, the template language will always be as simple as possible.
17
+ - The template language is declarative, and is going to stay that way.
data/README.md CHANGED
@@ -34,9 +34,11 @@ at Zendesk, where it performs admirably.
34
34
 
35
35
  1. [Installing](#installing)
36
36
  2. [How to use Curly](#how-to-use-curly)
37
+ 1. [Conditional blocks](#conditional-blocks)
38
+ 2. [Escaping Curly syntax](#escaping-curly-syntax)
37
39
  3. [Presenters](#presenters)
38
40
  1. [Layouts and Content Blocks](#layouts-and-content-blocks)
39
- 1. [Examples](#examples)
41
+ 2. [Examples](#examples)
40
42
  4. [Caching](#caching)
41
43
 
42
44
 
@@ -71,6 +73,10 @@ Add some HTML to the partial template along with some Curly variables:
71
73
  </p>
72
74
 
73
75
  {{body}}
76
+
77
+ {{#author?}}
78
+ <a href="{{delete_url}}">delete comment</a>
79
+ {{/author?}}
74
80
  </div>
75
81
  ```
76
82
 
@@ -93,6 +99,10 @@ class Posts::CommentPresenter < Curly::Presenter
93
99
  def time_ago
94
100
  time_ago_in_words(@comment.created_at)
95
101
  end
102
+
103
+ def author?
104
+ @comment.author == current_user
105
+ end
96
106
  end
97
107
  ```
98
108
 
@@ -105,9 +115,36 @@ render collection: post.comments
105
115
  ```
106
116
 
107
117
 
118
+ ### Conditional blocks
119
+
120
+ If there is some content you only want rendered under specific circumstances, you can
121
+ use _conditional blocks_. The `{{#admin?}}...{{/admin?}}` syntax will only render the
122
+ content of the block if the `admin?` method on the presenter returns true, while the
123
+ `{{^admin?}}...{{/admin?}}` syntax will only render the content if it returns false.
124
+
125
+ Both forms can be parameterized by a single argument: `{{#locale.en?}}...{{/locale.en?}}`
126
+ will only render the block if the `locale?` method on the presenter returns true given the
127
+ argument `"en"`. Here's how to implement that method in the presenter:
128
+
129
+ ```ruby
130
+ class SomePresenter < Curly::Presenter
131
+ # Allows rendering content only if the locale matches a specified identifier.
132
+ def locale?(identifier)
133
+ current_locale == identifier
134
+ end
135
+ end
136
+ ```
137
+
138
+
108
139
  ### Escaping Curly syntax
109
140
 
110
- If you need to have `{{` rendered, you should use `{{{` in your Curly template.
141
+ In order to have `{{` appear verbatim in the rendered HTML, use the triple Curly escape syntax:
142
+
143
+ ```
144
+ This is {{{escaped}}.
145
+ ```
146
+
147
+ You don't need to escape the closing `}}`.
111
148
 
112
149
 
113
150
  Presenters
@@ -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.12.0'
8
- s.date = '2013-12-03'
7
+ s.version = '1.0.0rc1'
8
+ s.date = '2014-02-18'
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."
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  # = MANIFEST =
30
30
  s.files = %w[
31
31
  CHANGELOG.md
32
+ CONTRIBUTING.md
32
33
  Gemfile
33
34
  README.md
34
35
  Rakefile
@@ -38,6 +39,10 @@ Gem::Specification.new do |s|
38
39
  lib/curly/compilation_error.rb
39
40
  lib/curly/compiler.rb
40
41
  lib/curly/dependency_tracker.rb
42
+ lib/curly/error.rb
43
+ lib/curly/incomplete_block_error.rb
44
+ lib/curly/incorrect_ending_error.rb
45
+ lib/curly/invalid_block_error.rb
41
46
  lib/curly/invalid_reference.rb
42
47
  lib/curly/presenter.rb
43
48
  lib/curly/railtie.rb
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.12.0"
29
+ VERSION = "1.0.0rc1"
30
30
 
31
31
  # Compiles a Curly template to Ruby code.
32
32
  #
@@ -1,5 +1,7 @@
1
+ require 'curly/error'
2
+
1
3
  module Curly
2
- class CompilationError < StandardError
4
+ class CompilationError < Error
3
5
  attr_reader :path
4
6
 
5
7
  def initialize(path)
@@ -1,5 +1,9 @@
1
1
  require 'curly/scanner'
2
+ require 'curly/error'
2
3
  require 'curly/invalid_reference'
4
+ require 'curly/invalid_block_error'
5
+ require 'curly/incorrect_ending_error'
6
+ require 'curly/incomplete_block_error'
3
7
 
4
8
  module Curly
5
9
 
@@ -30,7 +34,7 @@ module Curly
30
34
  compile(template, presenter_class)
31
35
 
32
36
  true
33
- rescue InvalidReference
37
+ rescue Error
34
38
  false
35
39
  end
36
40
 
@@ -47,10 +51,16 @@ module Curly
47
51
 
48
52
  tokens = Scanner.scan(template)
49
53
 
54
+ @blocks = []
55
+
50
56
  parts = tokens.map do |type, value|
51
57
  send("compile_#{type}", value)
52
58
  end
53
59
 
60
+ if @blocks.any?
61
+ raise IncompleteBlockError.new(@blocks.pop)
62
+ end
63
+
54
64
  <<-RUBY
55
65
  buffer = ActiveSupport::SafeBuffer.new
56
66
  #{parts.join("\n")}
@@ -60,6 +70,47 @@ module Curly
60
70
 
61
71
  private
62
72
 
73
+ def compile_block_start(reference)
74
+ compile_conditional_block "if", reference
75
+ end
76
+
77
+ def compile_inverse_block_start(reference)
78
+ compile_conditional_block "unless", reference
79
+ end
80
+
81
+ def compile_conditional_block(keyword, reference)
82
+ m = reference.match(/\A(.+?)(?:\.(.+))?\?\z/)
83
+ method, argument = "#{m[1]}?", m[2]
84
+
85
+ @blocks.push reference
86
+
87
+ unless presenter_class.method_available?(method.to_sym)
88
+ raise Curly::InvalidReference.new(method.to_sym)
89
+ end
90
+
91
+ if presenter_class.instance_method(method).arity == 1
92
+ <<-RUBY
93
+ #{keyword} presenter.#{method}(#{argument.inspect})
94
+ RUBY
95
+ else
96
+ <<-RUBY
97
+ #{keyword} presenter.#{method}
98
+ RUBY
99
+ end
100
+ end
101
+
102
+ def compile_block_end(reference)
103
+ last_block = @blocks.pop
104
+
105
+ unless last_block == reference
106
+ raise Curly::IncorrectEndingError.new(reference, last_block)
107
+ end
108
+
109
+ <<-RUBY
110
+ end
111
+ RUBY
112
+ end
113
+
63
114
  def compile_reference(reference)
64
115
  method, argument = reference.split(".", 2)
65
116
 
@@ -0,0 +1,4 @@
1
+ module Curly
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Curly
2
+ class IncompleteBlockError < Error
3
+ def initialize(reference)
4
+ @reference = reference
5
+ end
6
+
7
+ def message
8
+ "error compiling `{{##{@reference}}}`: conditional block must be terminated with `{{/#{@reference}}}}`"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Curly
2
+ class IncorrectEndingError < Error
3
+ def initialize(reference, last_block)
4
+ @reference, @last_block = reference, last_block
5
+ end
6
+
7
+ def message
8
+ "error compiling `{{##{@last_block}}}`: expected `{{/#{@last_block}}}`, got `{{/#{@reference}}}`"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Curly
2
+ class InvalidBlockError < Error
3
+ attr_reader :reference
4
+
5
+ def initialize(reference)
6
+ @reference = reference
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  module Curly
2
- class InvalidReference < StandardError
2
+ class InvalidReference < Error
3
3
  attr_reader :reference
4
4
 
5
5
  def initialize(reference)
data/lib/curly/scanner.rb CHANGED
@@ -15,6 +15,10 @@ module Curly
15
15
  ESCAPED_CURLY_START = /\{\{\{/
16
16
 
17
17
  COMMENT_MARKER = /!/
18
+ BLOCK_MARKER = /#/
19
+ INVERSE_BLOCK_MARKER = /\^/
20
+ END_BLOCK_MARKER = /\//
21
+
18
22
 
19
23
  # Scans a Curly template for tokens.
20
24
  #
@@ -59,13 +63,19 @@ module Curly
59
63
 
60
64
  def scan_curly
61
65
  if @scanner.scan(CURLY_START)
62
- scan_reference_or_comment or syntax_error!
66
+ scan_tag or syntax_error!
63
67
  end
64
68
  end
65
69
 
66
- def scan_reference_or_comment
70
+ def scan_tag
67
71
  if @scanner.scan(COMMENT_MARKER)
68
72
  scan_comment
73
+ elsif @scanner.scan(BLOCK_MARKER)
74
+ scan_block_start
75
+ elsif @scanner.scan(INVERSE_BLOCK_MARKER)
76
+ scan_inverse_block_start
77
+ elsif @scanner.scan(END_BLOCK_MARKER)
78
+ scan_block_end
69
79
  else
70
80
  scan_reference
71
81
  end
@@ -77,6 +87,24 @@ module Curly
77
87
  end
78
88
  end
79
89
 
90
+ def scan_block_start
91
+ if value = scan_until_end_of_curly
92
+ [:block_start, value]
93
+ end
94
+ end
95
+
96
+ def scan_inverse_block_start
97
+ if value = scan_until_end_of_curly
98
+ [:inverse_block_start, value]
99
+ end
100
+ end
101
+
102
+ def scan_block_end
103
+ if value = scan_until_end_of_curly
104
+ [:block_end, value]
105
+ end
106
+ end
107
+
80
108
  def scan_reference
81
109
  if value = scan_until_end_of_curly
82
110
  [:reference, value]
@@ -1,5 +1,7 @@
1
+ require 'curly/error'
2
+
1
3
  module Curly
2
- class SyntaxError < StandardError
4
+ class SyntaxError < Error
3
5
  def initialize(position, source)
4
6
  @position, @source = position, source
5
7
  end
@@ -15,6 +15,10 @@ describe Curly::Compiler do
15
15
  "#{yield :foo}, please?"
16
16
  end
17
17
 
18
+ def hello?(value)
19
+ value == "world"
20
+ end
21
+
18
22
  def unicorns
19
23
  "UNICORN"
20
24
  end
@@ -23,12 +27,21 @@ describe Curly::Compiler do
23
27
  nil
24
28
  end
25
29
 
30
+ def false?
31
+ false
32
+ end
33
+
34
+ def true?
35
+ true
36
+ end
37
+
26
38
  def parameterized(value)
27
39
  value
28
40
  end
29
41
 
30
42
  def self.method_available?(method)
31
- [:foo, :parameterized, :high_yield, :yield_value, :dirty].include?(method)
43
+ [:foo, :parameterized, :high_yield, :yield_value, :dirty,
44
+ :false?, :true?, :hello?].include?(method)
32
45
  end
33
46
 
34
47
  def self.available_methods
@@ -108,6 +121,44 @@ describe Curly::Compiler do
108
121
  evaluate("HELO{{! I'm a comment, yo }}WORLD").should == "HELOWORLD"
109
122
  end
110
123
 
124
+ it "removes text in false blocks" do
125
+ evaluate("test{{#false?}}bar{{/false?}}").should == "test"
126
+ end
127
+
128
+ it "keeps text in true blocks" do
129
+ evaluate("test{{#true?}}bar{{/true?}}").should == "testbar"
130
+ end
131
+
132
+ it "removes text in inverse true blocks" do
133
+ evaluate("test{{^true?}}bar{{/true?}}").should == "test"
134
+ end
135
+
136
+ it "keeps kext in inverse false blocks" do
137
+ evaluate("test{{^false?}}bar{{/false?}}").should == "testbar"
138
+ end
139
+
140
+ it "passes an argument to blocks" do
141
+ evaluate("{{#hello.world?}}foo{{/hello.world?}}{{#hello.foo?}}bar{{/hello.foo?}}").should == "foo"
142
+ end
143
+
144
+ it "gives an error on mismatching blocks" do
145
+ expect do
146
+ evaluate("test{{#false?}}bar{{/true?}}")
147
+ end.to raise_exception(Curly::IncorrectEndingError)
148
+ end
149
+
150
+ it "gives an error on incomplete blocks" do
151
+ expect do
152
+ evaluate("test{{#false?}}bar")
153
+ end.to raise_exception(Curly::IncompleteBlockError)
154
+ end
155
+
156
+ it "gives an error on mismatching block ends" do
157
+ expect do
158
+ evaluate("{{#true?}}test{{#false?}}bar{{/true?}}{{/false?}}")
159
+ end.to raise_exception(Curly::IncorrectEndingError)
160
+ end
161
+
111
162
  it "does not execute arbitrary Ruby code" do
112
163
  evaluate('#{foo}').should == '#{foo}'
113
164
  end
@@ -127,6 +178,14 @@ describe Curly::Compiler do
127
178
  validate("Hello, {{inspect}}").should == false
128
179
  end
129
180
 
181
+ it "returns true with a block" do
182
+ validate("Hello {{#true?}}world{{/true?}}").should == true
183
+ end
184
+
185
+ it "returns false with an incomplete block" do
186
+ validate("Hello {{#true?}}world").should == false
187
+ end
188
+
130
189
  def validate(template)
131
190
  Curly.valid?(template, presenter_class)
132
191
  end
@@ -19,28 +19,38 @@ describe Curly::Presenter do
19
19
  presents :elephant, default: "Babar"
20
20
  end
21
21
 
22
- it "sets the presented parameters as instance variables" do
23
- context = double("context")
22
+ class FancyCircusPresenter < CircusPresenter
23
+ presents :champagne
24
+ end
24
25
 
25
- presenter = CircusPresenter.new(context,
26
- midget: "Meek Harolson",
27
- clown: "Bubbles"
28
- )
26
+ describe "#initialize" do
27
+ let(:context) { double("context") }
29
28
 
30
- presenter.midget.should == "Meek Harolson"
31
- presenter.clown.should == "Bubbles"
32
- end
29
+ it "sets the presented parameters as instance variables" do
30
+ presenter = CircusPresenter.new(context,
31
+ midget: "Meek Harolson",
32
+ clown: "Bubbles"
33
+ )
34
+
35
+ presenter.midget.should == "Meek Harolson"
36
+ presenter.clown.should == "Bubbles"
37
+ end
33
38
 
34
- it "allows specifying default values for parameters" do
35
- context = double("context")
39
+ it "raises an exception if a required parameter is not specified" do
40
+ expect {
41
+ FancyCircusPresenter.new(context, {})
42
+ }.to raise_exception(ArgumentError, "required parameter `champagne` missing")
43
+ end
36
44
 
37
- # Make sure subclasses can change default values.
38
- french_presenter = FrenchCircusPresenter.new(context)
39
- french_presenter.elephant.should == "Babar"
45
+ it "allows specifying default values for parameters" do
46
+ # Make sure subclasses can change default values.
47
+ french_presenter = FrenchCircusPresenter.new(context)
48
+ french_presenter.elephant.should == "Babar"
40
49
 
41
- # The subclass shouldn't change the superclass' defaults, though.
42
- presenter = CircusPresenter.new(context)
43
- presenter.elephant.should == "Dumbo"
50
+ # The subclass shouldn't change the superclass' defaults, though.
51
+ presenter = CircusPresenter.new(context)
52
+ presenter.elephant.should == "Dumbo"
53
+ end
44
54
  end
45
55
 
46
56
  describe ".presenter_for_path" do
data/spec/scanner_spec.rb CHANGED
@@ -59,6 +59,24 @@ describe Curly::Scanner, ".scan" do
59
59
  ]
60
60
  end
61
61
 
62
+ it "scans block tags" do
63
+ scan('foo {{#bar}} hello {{/bar}}').should == [
64
+ [:text, "foo "],
65
+ [:block_start, "bar"],
66
+ [:text, " hello "],
67
+ [:block_end, "bar"]
68
+ ]
69
+ end
70
+
71
+ it "scans inverse block tags" do
72
+ scan('foo {{^bar}} hello {{/bar}}').should == [
73
+ [:text, "foo "],
74
+ [:inverse_block_start, "bar"],
75
+ [:text, " hello "],
76
+ [:block_end, "bar"]
77
+ ]
78
+ end
79
+
62
80
  it "treats quotes as text" do
63
81
  scan('"').should == [
64
82
  [:text, '"']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curly-templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.0.0rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-03 00:00:00.000000000 Z
11
+ date: 2014-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -99,6 +99,7 @@ extensions: []
99
99
  extra_rdoc_files: []
100
100
  files:
101
101
  - CHANGELOG.md
102
+ - CONTRIBUTING.md
102
103
  - Gemfile
103
104
  - README.md
104
105
  - Rakefile
@@ -108,6 +109,10 @@ files:
108
109
  - lib/curly/compilation_error.rb
109
110
  - lib/curly/compiler.rb
110
111
  - lib/curly/dependency_tracker.rb
112
+ - lib/curly/error.rb
113
+ - lib/curly/incomplete_block_error.rb
114
+ - lib/curly/incorrect_ending_error.rb
115
+ - lib/curly/invalid_block_error.rb
111
116
  - lib/curly/invalid_reference.rb
112
117
  - lib/curly/presenter.rb
113
118
  - lib/curly/railtie.rb
@@ -141,12 +146,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
146
  version: '0'
142
147
  required_rubygems_version: !ruby/object:Gem::Requirement
143
148
  requirements:
144
- - - '>='
149
+ - - '>'
145
150
  - !ruby/object:Gem::Version
146
- version: '0'
151
+ version: 1.3.1
147
152
  requirements: []
148
153
  rubyforge_project:
149
- rubygems_version: 2.1.11
154
+ rubygems_version: 2.0.14
150
155
  signing_key:
151
156
  specification_version: 2
152
157
  summary: Free your views!