curly-templates 2.0.1 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +2 -0
  4. data/README.md +85 -5
  5. data/curly-templates.gemspec +37 -8
  6. data/lib/curly.rb +1 -1
  7. data/lib/curly/{attribute_parser.rb → attribute_scanner.rb} +6 -4
  8. data/lib/curly/compiler.rb +81 -72
  9. data/lib/curly/component_compiler.rb +37 -31
  10. data/lib/curly/component_scanner.rb +19 -0
  11. data/lib/curly/incomplete_block_error.rb +0 -7
  12. data/lib/curly/incorrect_ending_error.rb +0 -21
  13. data/lib/curly/parser.rb +171 -0
  14. data/lib/curly/presenter.rb +1 -1
  15. data/lib/curly/scanner.rb +23 -9
  16. data/spec/attribute_scanner_spec.rb +46 -0
  17. data/spec/collection_blocks_spec.rb +88 -0
  18. data/spec/compiler/context_blocks_spec.rb +42 -0
  19. data/spec/component_compiler_spec.rb +26 -77
  20. data/spec/component_scanner_spec.rb +19 -0
  21. data/spec/{integration/components_spec.rb → components_spec.rb} +0 -0
  22. data/spec/{integration/conditional_blocks_spec.rb → conditional_blocks_spec.rb} +0 -0
  23. data/spec/dummy/.gitignore +1 -0
  24. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  25. data/spec/dummy/app/controllers/dashboards_controller.rb +13 -0
  26. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  27. data/spec/dummy/app/presenters/dashboards/collection_presenter.rb +7 -0
  28. data/spec/dummy/app/presenters/dashboards/item_presenter.rb +7 -0
  29. data/spec/dummy/app/presenters/dashboards/new_presenter.rb +19 -0
  30. data/spec/dummy/app/presenters/dashboards/partials_presenter.rb +5 -0
  31. data/spec/dummy/app/presenters/dashboards/show_presenter.rb +12 -0
  32. data/spec/dummy/app/presenters/layouts/application_presenter.rb +9 -0
  33. data/spec/dummy/app/views/dashboards/_item.html.curly +1 -0
  34. data/spec/dummy/app/views/dashboards/collection.html.curly +5 -0
  35. data/spec/dummy/app/views/dashboards/new.html.curly +3 -0
  36. data/spec/dummy/app/views/dashboards/partials.html.curly +3 -0
  37. data/spec/dummy/app/views/dashboards/show.html.curly +3 -0
  38. data/spec/dummy/app/views/layouts/application.html.curly +8 -0
  39. data/spec/dummy/config.ru +4 -0
  40. data/spec/dummy/config/application.rb +12 -0
  41. data/spec/dummy/config/boot.rb +5 -0
  42. data/spec/dummy/config/environment.rb +5 -0
  43. data/spec/dummy/config/environments/test.rb +36 -0
  44. data/spec/dummy/config/routes.rb +6 -0
  45. data/spec/integration/application_layout_spec.rb +21 -0
  46. data/spec/integration/collection_blocks_spec.rb +17 -78
  47. data/spec/integration/context_blocks_spec.rb +21 -0
  48. data/spec/integration/partials_spec.rb +23 -0
  49. data/spec/parser_spec.rb +95 -0
  50. data/spec/scanner_spec.rb +24 -14
  51. data/spec/spec_helper.rb +4 -3
  52. metadata +49 -14
  53. data/lib/curly/component_parser.rb +0 -13
  54. data/spec/attribute_parser_spec.rb +0 -46
  55. data/spec/incorrect_ending_error_spec.rb +0 -13
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Collection block components" do
4
+ include RenderingSupport
5
+
6
+ before do
7
+ item_presenter do
8
+ presents :item
9
+
10
+ def name
11
+ @item
12
+ end
13
+ end
14
+ end
15
+
16
+ example "with neither identifier nor attributes" do
17
+ presenter do
18
+ def items
19
+ ["one", "two", "three"]
20
+ end
21
+ end
22
+
23
+ render("{{*items}}<{{name}}>{{/items}}").should == "<one><two><three>"
24
+ end
25
+
26
+ example "with an identifier" do
27
+ presenter do
28
+ def items(filter = nil)
29
+ if filter == "even"
30
+ ["two"]
31
+ elsif filter == "odd"
32
+ ["one", "three"]
33
+ else
34
+ ["one", "two", "three"]
35
+ end
36
+ end
37
+ end
38
+
39
+ render("{{*items.even}}<{{name}}>{{/items.even}}").should == "<two>"
40
+ render("{{*items.odd}}<{{name}}>{{/items.odd}}").should == "<one><three>"
41
+ render("{{*items}}<{{name}}>{{/items}}").should == "<one><two><three>"
42
+ end
43
+
44
+ example "with attributes" do
45
+ presenter do
46
+ def items(length: "1")
47
+ ["x"] * length.to_i
48
+ end
49
+ end
50
+
51
+ render("{{*items length=3}}<{{name}}>{{/items}}").should == "<x><x><x>"
52
+ render("{{*items}}<{{name}}>{{/items}}").should == "<x>"
53
+ end
54
+
55
+ example "with nested collection blocks" do
56
+ presenter do
57
+ def items
58
+ [{ parts: [1, 2] }, { parts: [3, 4] }]
59
+ end
60
+ end
61
+
62
+ item_presenter do
63
+ presents :item
64
+
65
+ def parts
66
+ @item[:parts]
67
+ end
68
+ end
69
+
70
+ part_presenter do
71
+ presents :part
72
+
73
+ def number
74
+ @part
75
+ end
76
+ end
77
+
78
+ render("{{*items}}<{{*parts}}[{{number}}]{{/parts}}>{{/items}}").should == "<[1][2]><[3][4]>"
79
+ end
80
+
81
+ def item_presenter(&block)
82
+ stub_const("ItemPresenter", Class.new(Curly::Presenter, &block))
83
+ end
84
+
85
+ def part_presenter(&block)
86
+ stub_const("ItemPresenter::PartPresenter", Class.new(Curly::Presenter, &block))
87
+ end
88
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Curly::Compiler do
4
+ include CompilationSupport
5
+
6
+ let(:presenter_class) do
7
+ Class.new(Curly::Presenter) do
8
+ def form(&block)
9
+ "<form>".html_safe + block.call("yo") + "</form>".html_safe
10
+ end
11
+
12
+ def invalid
13
+ "uh oh!"
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:context_presenter_class) do
19
+ Class.new(Curly::Presenter) do
20
+ presents :form
21
+
22
+ def text
23
+ @form.upcase
24
+ end
25
+ end
26
+ end
27
+
28
+ let(:context) { double("context") }
29
+ let(:presenter) { presenter_class.new(context, {}) }
30
+
31
+ before do
32
+ stub_const("FormPresenter", context_presenter_class)
33
+ end
34
+
35
+ it "compiles context blocks" do
36
+ evaluate('{{@form}}{{text}}{{/form}}').should == '<form>YO</form>'
37
+ end
38
+
39
+ it "fails if the component is not a context block" do
40
+ expect { evaluate('{{@invalid}}yo{{/invalid}}') }.to raise_exception(Curly::Error)
41
+ end
42
+ end
@@ -1,71 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Curly::ComponentCompiler do
4
- describe ".compile_conditional" do
5
- let(:presenter_class) do
6
- Class.new do
7
- def monday?
8
- true
9
- end
10
-
11
- def tuesday?
12
- false
13
- end
14
-
15
- def day?(name)
16
- name == "monday"
17
- end
18
-
19
- def season?(name:)
20
- name == "summer"
21
- end
22
-
23
- def hello
24
- "hello"
25
- end
26
-
27
- def self.component_available?(name)
28
- true
29
- end
30
- end
31
- end
32
-
33
- it "compiles simple components" do
34
- evaluate("monday?").should == true
35
- evaluate("tuesday?").should == false
36
- end
37
-
38
- it "compiles components with an identifier" do
39
- evaluate("day.monday?").should == true
40
- evaluate("day.tuesday?").should == false
41
- end
42
-
43
- it "compiles components with attributes" do
44
- evaluate("season? name=summer").should == true
45
- evaluate("season? name=winter").should == false
46
- end
47
-
48
- it "fails if the component is missing a question mark" do
49
- expect { evaluate("hello") }.to raise_exception(Curly::Error)
50
- end
51
-
52
- def evaluate(component, &block)
53
- method, argument, attributes = Curly::ComponentParser.parse(component)
54
- code = Curly::ComponentCompiler.compile_conditional(presenter_class, method, argument, attributes)
55
- presenter = presenter_class.new
56
- context = double("context", presenter: presenter)
57
-
58
- context.instance_eval(<<-RUBY)
59
- def self.render
60
- #{code}
61
- end
62
- RUBY
63
-
64
- context.render(&block)
65
- end
66
- end
67
-
68
- describe ".compile_component" do
4
+ describe ".compile" do
69
5
  let(:presenter_class) do
70
6
  Class.new do
71
7
  def title
@@ -95,6 +31,10 @@ describe Curly::ComponentCompiler do
95
31
  s
96
32
  end
97
33
 
34
+ def form(&block)
35
+ "some form"
36
+ end
37
+
98
38
  def self.component_available?(name)
99
39
  true
100
40
  end
@@ -118,6 +58,10 @@ describe Curly::ComponentCompiler do
118
58
  evaluate("widget color=blue size=50px").should == "Widget (50px) - blue"
119
59
  end
120
60
 
61
+ it "compiles context block components" do
62
+ evaluate("form", type: :context).should == "some form"
63
+ end
64
+
121
65
  it "allows both identifier and attributes" do
122
66
  evaluate("i18n.hello fallback=yolo").should == "yolo"
123
67
  end
@@ -142,19 +86,24 @@ describe Curly::ComponentCompiler do
142
86
  expect { evaluate("invalid") }.to raise_exception(Curly::Error)
143
87
  end
144
88
 
145
- def evaluate(component, &block)
146
- method, argument, attributes = Curly::ComponentParser.parse(component)
147
- code = Curly::ComponentCompiler.compile_component(presenter_class, method, argument, attributes)
148
- presenter = presenter_class.new
149
- context = double("context", presenter: presenter)
89
+ it "fails when a context block component is used with a method that doesn't take a block" do
90
+ expect { evaluate("title", type: :context) }.to raise_exception(Curly::Error)
91
+ end
92
+ end
150
93
 
151
- context.instance_eval(<<-RUBY)
152
- def self.render
153
- #{code}
154
- end
155
- RUBY
94
+ def evaluate(text, type: nil, &block)
95
+ name, identifier, attributes = Curly::ComponentScanner.scan(text)
96
+ component = Curly::Parser::Component.new(name, identifier, attributes)
97
+ code = Curly::ComponentCompiler.compile(presenter_class, component, type: type)
98
+ presenter = presenter_class.new
99
+ context = double("context", presenter: presenter)
156
100
 
157
- context.render(&block)
158
- end
101
+ context.instance_eval(<<-RUBY)
102
+ def self.render
103
+ #{code}
104
+ end
105
+ RUBY
106
+
107
+ context.render(&block)
159
108
  end
160
109
  end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Curly::ComponentScanner do
4
+ it "scans the component name, identifier, and attributes" do
5
+ scan('hello.world weather="sunny"').should == [
6
+ "hello",
7
+ "world",
8
+ { "weather" => "sunny" }
9
+ ]
10
+ end
11
+
12
+ it "allows a question mark after the identifier" do
13
+ scan('hello.world?').should == ["hello?", "world", {}]
14
+ end
15
+
16
+ def scan(component)
17
+ described_class.scan(component)
18
+ end
19
+ end
@@ -0,0 +1 @@
1
+ log
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,13 @@
1
+ class DashboardsController < ApplicationController
2
+ def show
3
+ @message = "Hello, World!"
4
+ end
5
+
6
+ def collection
7
+ @items = ["uno", "dos", "tres!"]
8
+ end
9
+
10
+ def new
11
+ @name = "test"
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module ApplicationHelper
2
+ def welcome_message
3
+ "Welcome!"
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Dashboards::CollectionPresenter < Curly::Presenter
2
+ presents :items
3
+
4
+ def items
5
+ @items
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Dashboards::ItemPresenter < Curly::Presenter
2
+ presents :item
3
+
4
+ def item
5
+ @item
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class Dashboards::NewPresenter < Curly::Presenter
2
+ presents :name
3
+
4
+ def form(&block)
5
+ form_for(:dashboard, &block)
6
+ end
7
+
8
+ class FormPresenter < Curly::Presenter
9
+ presents :form, :name
10
+
11
+ def name_label
12
+ "Name"
13
+ end
14
+
15
+ def name_input
16
+ @form.text_field :name, value: @name
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ class Dashboards::PartialsPresenter < Curly::Presenter
2
+ def items
3
+ render partial: 'item', collection: ["One", "Two"]
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ class Dashboards::ShowPresenter < Curly::Presenter
2
+ presents :message
3
+
4
+ def message
5
+ @message
6
+ end
7
+
8
+ def welcome
9
+ # This is a helper method:
10
+ welcome_message
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ class Layouts::ApplicationPresenter < Curly::Presenter
2
+ def title
3
+ "Dummy app"
4
+ end
5
+
6
+ def content
7
+ yield
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ <li>{{item}}</li>
@@ -0,0 +1,5 @@
1
+ <ul>
2
+ {{*items}}
3
+ <li>{{item}}</li>
4
+ {{/items}}
5
+ </ul>
@@ -0,0 +1,3 @@
1
+ {{@form}}
2
+ <b>{{name_label}}</b> {{name_input}}
3
+ {{/form}}
@@ -0,0 +1,3 @@
1
+ <ul>
2
+ {{items}}
3
+ </ul>
@@ -0,0 +1,3 @@
1
+ <h1>Dashboard</h1>
2
+ <p>{{message}}</p>
3
+ <p>{{welcome}}</p>
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>{{title}}</title>
4
+ </head>
5
+ <body>
6
+ {{content}}
7
+ </body>
8
+ </html>
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Rails.application
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'action_controller/railtie'
4
+
5
+ Bundler.require(*Rails.groups)
6
+ require "curly"
7
+
8
+ module Dummy
9
+ class Application < Rails::Application
10
+ end
11
+ end
12
+
@@ -0,0 +1,5 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
+
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
+ $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)