curly-templates 0.12.0 → 1.0.0rc1

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.
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!