lotus-view 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -15
  3. data/.travis.yml +6 -0
  4. data/.yardopts +3 -0
  5. data/Gemfile +13 -2
  6. data/README.md +514 -3
  7. data/Rakefile +17 -1
  8. data/lib/lotus/layout.rb +132 -0
  9. data/lib/lotus/presenter.rb +70 -0
  10. data/lib/lotus/view/dsl.rb +247 -0
  11. data/lib/lotus/view/inheritable.rb +50 -0
  12. data/lib/lotus/view/rendering/layout_finder.rb +104 -0
  13. data/lib/lotus/view/rendering/layout_registry.rb +63 -0
  14. data/lib/lotus/view/rendering/layout_scope.rb +138 -0
  15. data/lib/lotus/view/rendering/null_layout.rb +52 -0
  16. data/lib/lotus/view/rendering/null_template.rb +79 -0
  17. data/lib/lotus/view/rendering/partial.rb +29 -0
  18. data/lib/lotus/view/rendering/partial_finder.rb +41 -0
  19. data/lib/lotus/view/rendering/registry.rb +129 -0
  20. data/lib/lotus/view/rendering/scope.rb +48 -0
  21. data/lib/lotus/view/rendering/template.rb +56 -0
  22. data/lib/lotus/view/rendering/template_finder.rb +53 -0
  23. data/lib/lotus/view/rendering/templates_finder.rb +85 -0
  24. data/lib/lotus/view/rendering/view_finder.rb +37 -0
  25. data/lib/lotus/view/rendering.rb +265 -0
  26. data/lib/lotus/view/template.rb +45 -0
  27. data/lib/lotus/view/version.rb +4 -1
  28. data/lib/lotus/view.rb +180 -2
  29. data/lib/lotus-view.rb +1 -0
  30. data/lotus-view.gemspec +15 -11
  31. data/test/fixtures/templates/app/app_view.html.erb +0 -0
  32. data/test/fixtures/templates/app/view.html.erb +0 -0
  33. data/test/fixtures/templates/application.html.erb +10 -0
  34. data/test/fixtures/templates/articles/_form.html.erb +4 -0
  35. data/test/fixtures/templates/articles/alternative_new.html.erb +1 -0
  36. data/test/fixtures/templates/articles/index.atom.erb +5 -0
  37. data/test/fixtures/templates/articles/index.html.erb +3 -0
  38. data/test/fixtures/templates/articles/index.json.erb +9 -0
  39. data/test/fixtures/templates/articles/index.rss.erb +0 -0
  40. data/test/fixtures/templates/articles/new.html.erb +7 -0
  41. data/test/fixtures/templates/articles/show.html.erb +1 -0
  42. data/test/fixtures/templates/articles/show.json.erb +5 -0
  43. data/test/fixtures/templates/contacts/show.html.haml +1 -0
  44. data/test/fixtures/templates/dashboard/index.html.erb +2 -0
  45. data/test/fixtures/templates/hello_world_view.html.erb +1 -0
  46. data/test/fixtures/templates/index_view.html.erb +1 -0
  47. data/test/fixtures/templates/json_render_view.json.erb +3 -0
  48. data/test/fixtures/templates/render_view.html.erb +1 -0
  49. data/test/fixtures/templates/shared/_sidebar.html.erb +1 -0
  50. data/test/fixtures.rb +187 -0
  51. data/test/layout_test.rb +10 -0
  52. data/test/load_test.rb +79 -0
  53. data/test/presenter_test.rb +31 -0
  54. data/test/rendering_test.rb +125 -0
  55. data/test/root_test.rb +38 -0
  56. data/test/test_helper.rb +24 -0
  57. data/test/version_test.rb +7 -0
  58. data/test/view_test.rb +27 -0
  59. metadata +137 -10
@@ -0,0 +1,132 @@
1
+ require 'lotus/view/rendering/layout_registry'
2
+ require 'lotus/view/rendering/layout_scope'
3
+ require 'lotus/view/rendering/null_layout'
4
+
5
+ module Lotus
6
+ # Layout
7
+ #
8
+ # @since 0.1.0
9
+ #
10
+ # @see Lotus::Layout::ClassMethods
11
+ module Layout
12
+ # Register a layout
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ #
17
+ # @example
18
+ # require 'lotus/view'
19
+ #
20
+ # class ApplicationLayout
21
+ # include Lotus::Layout
22
+ # end
23
+ def self.included(base)
24
+ base.class_eval do
25
+ extend Lotus::View::Dsl.dup
26
+ extend ClassMethods
27
+ end
28
+
29
+ Lotus::View.layouts.add(base)
30
+ end
31
+
32
+ # Class level API
33
+ #
34
+ # @since 0.1.0
35
+ module ClassMethods
36
+ # Template name suffix
37
+ #
38
+ # @api private
39
+ # @since 0.1.0
40
+ #
41
+ # @see Lotus::Layout::ClassMethods#suffix
42
+ # @see Lotus::Layout::ClassMethods#template
43
+ SUFFIX = '_layout'.freeze
44
+
45
+ # A registry that holds all the registered layouts.
46
+ #
47
+ # @api private
48
+ # @since 0.1.0
49
+ #
50
+ # @see Lotus::View::Rendering::LayoutRegistry
51
+ def registry
52
+ @@registry ||= View::Rendering::LayoutRegistry.new(self)
53
+ end
54
+
55
+ # Template name
56
+ #
57
+ # @api private
58
+ # @since 0.1.0
59
+ #
60
+ # @see Lotus::Layout::ClassMethods#SUFFIX
61
+ # @see Lotus::Layout::ClassMethods#suffix
62
+ #
63
+ # @example
64
+ # # Given a template 'templates/application.html.erb'
65
+ #
66
+ # class ApplicationLayout
67
+ # include Lotus::Layout
68
+ # end
69
+ #
70
+ # ApplicationLayout.template # => 'application'
71
+ def template
72
+ super.gsub(suffix, '')
73
+ end
74
+
75
+ # Template name suffix
76
+ #
77
+ # @api private
78
+ # @since 0.1.0
79
+ #
80
+ # @see Lotus::Layout::ClassMethods#SUFFIX
81
+ # @see Lotus::Layout::ClassMethods#template
82
+ def suffix
83
+ SUFFIX
84
+ end
85
+
86
+ protected
87
+ # Loading mechanism hook.
88
+ #
89
+ # @api private
90
+ # @since 0.1.0
91
+ #
92
+ # @see Lotus::View.load!
93
+ def load!
94
+ registry.freeze
95
+ end
96
+ end
97
+
98
+ # Initialize a layout
99
+ #
100
+ # @param scope [Lotus::View::Rendering::Scope] view rendering scope
101
+ # @param rendered [String] the output of the view rendering process
102
+ #
103
+ # @api private
104
+ # @since 0.1.0
105
+ #
106
+ # @see Lotus::View::Rendering#render
107
+ def initialize(scope, rendered)
108
+ @scope, @rendered = View::Rendering::LayoutScope.new(self, scope), rendered
109
+ end
110
+
111
+ # Render the layout
112
+ #
113
+ # @return [String] the output of the rendering process
114
+ #
115
+ # @api private
116
+ # @since 0.1.0
117
+ #
118
+ # @see Lotus::View::Rendering#render
119
+ def render
120
+ template.render(@scope, &Proc.new{@rendered})
121
+ end
122
+
123
+ protected
124
+ # The template for the current format
125
+ #
126
+ # @api private
127
+ # @since 0.1.0
128
+ def template
129
+ self.class.registry.resolve({format: @scope.format})
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,70 @@
1
+ module Lotus
2
+ # Presenter pattern implementation
3
+ #
4
+ # @since 0.1.0
5
+ #
6
+ # @example
7
+ # require 'lotus/view'
8
+ #
9
+ # class Map
10
+ # attr_reader :locations
11
+ #
12
+ # def initialize(locations)
13
+ # @locations = locations
14
+ # end
15
+ #
16
+ # def location_names
17
+ # @locations.join(', ')
18
+ # end
19
+ # end
20
+ #
21
+ # class MapPresenter
22
+ # include Lotus::Presenter
23
+ #
24
+ # def count
25
+ # locations.count
26
+ # end
27
+ #
28
+ # def location_names
29
+ # super.upcase
30
+ # end
31
+ #
32
+ # def inspect_object
33
+ # @object.inspect
34
+ # end
35
+ # end
36
+ #
37
+ # map = Map.new(['Rome', 'Boston'])
38
+ # presenter = MapPresenter.new(map)
39
+ #
40
+ # # access a map method
41
+ # puts presenter.locations # => ['Rome', 'Boston']
42
+ #
43
+ # # access presenter concrete methods
44
+ # puts presenter.count # => 1
45
+ #
46
+ # # uses super to access original object implementation
47
+ # puts presenter.location_names # => 'ROME, BOSTON'
48
+ #
49
+ # # it has private access to the original object
50
+ # puts presenter.inspect_object # => #<Map:0x007fdeada0b2f0 @locations=["Rome", "Boston"]>
51
+ module Presenter
52
+ # Initialize the presenter
53
+ #
54
+ # @param object [Object] the object to present
55
+ #
56
+ # @since 0.1.0
57
+ def initialize(object)
58
+ @object = object
59
+ end
60
+
61
+ protected
62
+ def method_missing(m, *args, &blk)
63
+ if @object.respond_to?(m)
64
+ @object.__send__ m, *args, &blk
65
+ else
66
+ super
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,247 @@
1
+ require 'lotus/utils/string'
2
+ require 'lotus/view/rendering/layout_finder'
3
+
4
+ module Lotus
5
+ module View
6
+ # Class level DSL
7
+ #
8
+ # @since 0.1.0
9
+ module Dsl
10
+ # When a value is given, specify a templates root path for the view.
11
+ # Otherwise, it returns templates root path.
12
+ #
13
+ # When not initialized, it will return the global value from `Lotus::View.root`.
14
+ #
15
+ # @param value [String] the templates root for this view
16
+ #
17
+ # @return [Pathname] the specified root for this view or the global value
18
+ #
19
+ # @since 0.1.0
20
+ #
21
+ # @example Default usage
22
+ # require 'lotus/view'
23
+ #
24
+ # module Articles
25
+ # class Show
26
+ # include Lotus::View
27
+ # end
28
+ # end
29
+ #
30
+ # Lotus::View.root # => 'app/templates'
31
+ # Articles::Show.root # => 'app/templates'
32
+ #
33
+ # @example Custom root
34
+ # require 'lotus/view'
35
+ #
36
+ # module Articles
37
+ # class Show
38
+ # include Lotus::View
39
+ # root 'path/to/articles/templates'
40
+ # end
41
+ # end
42
+ #
43
+ # Lotus::View.root # => 'app/templates'
44
+ # Articles::Show.root # => 'path/to/articles/templates'
45
+ def root(value = nil)
46
+ if value
47
+ @@root = Pathname.new value
48
+ else
49
+ @@root ||= Lotus::View.root
50
+ end
51
+ end
52
+
53
+ # When a value is given, specify the handled format.
54
+ # Otherwise, it returns the previously specified format.
55
+ #
56
+ # @param value [Symbol] the format
57
+ #
58
+ # @return [Symbol, nil] the specified format for this view, if set
59
+ #
60
+ # @since 0.1.0
61
+ #
62
+ # @example
63
+ # require 'lotus/view'
64
+ #
65
+ # module Articles
66
+ # class Show
67
+ # include Lotus::View
68
+ # end
69
+ #
70
+ # class JsonShow < Show
71
+ # format :json
72
+ # end
73
+ # end
74
+ #
75
+ # Articles::Show.format # => nil
76
+ # Articles::JsonShow.format # => :json
77
+ def format(value = nil)
78
+ if value
79
+ @format = value
80
+ else
81
+ @format
82
+ end
83
+ end
84
+
85
+ # When a value is given, specify the relative path to the template.
86
+ # Otherwise, it returns the name that follows Lotus::View conventions.
87
+ #
88
+ # @param value [String] relative template path
89
+ #
90
+ # @return [String] the specified template for this view or the name
91
+ # that follows the convention
92
+ #
93
+ # @since 0.1.0
94
+ #
95
+ # @example Default usage
96
+ # require 'lotus/view'
97
+ #
98
+ # module Articles
99
+ # class Show
100
+ # include Lotus::View
101
+ # end
102
+ #
103
+ # class JsonShow < Show
104
+ # format :json
105
+ # end
106
+ # end
107
+ #
108
+ # Articles::Show.template # => 'articles/show'
109
+ # Articles::JsonShow.template # => 'articles/show'
110
+ #
111
+ # @example Custom template
112
+ # require 'lotus/view'
113
+ #
114
+ # module Articles
115
+ # class Show
116
+ # include Lotus::View
117
+ # template 'articles/single_article'
118
+ # end
119
+ #
120
+ # class JsonShow < Show
121
+ # format :json
122
+ # end
123
+ # end
124
+ #
125
+ # Articles::Show.template # => 'articles/single_article'
126
+ # Articles::JsonShow.template # => 'articles/single_article'
127
+ def template(value = nil)
128
+ if value
129
+ @@template = value
130
+ else
131
+ @@template ||= Utils::String.new(name).underscore
132
+ end
133
+ end
134
+
135
+ # When a value is given, it specifies the layout.
136
+ # Otherwise, it returns the previously specified layout.
137
+ #
138
+ # When the global configuration is set (`Lotus::View.layout=`), after the
139
+ # loading process, it will return that layout if not otherwise specified.
140
+ #
141
+ # @param value [Symbol,nil] the layout name
142
+ #
143
+ # @return [Symbol, nil] the specified layout for this view, if set
144
+ #
145
+ # @since 0.1.0
146
+ #
147
+ # @see Lotus::Layout
148
+ #
149
+ # @example Default usage
150
+ # require 'lotus/view'
151
+ #
152
+ # module Articles
153
+ # class Show
154
+ # include Lotus::View
155
+ # end
156
+ # end
157
+ #
158
+ # Articles::Show.layout # => nil
159
+ #
160
+ # @example Custom layout
161
+ # require 'lotus/view'
162
+ #
163
+ # class ArticlesLayout
164
+ # include Lotus::Layout
165
+ # end
166
+ #
167
+ # module Articles
168
+ # class Show
169
+ # include Lotus::View
170
+ # layout :articles
171
+ # end
172
+ # end
173
+ #
174
+ # Articles::Show.layout # => :articles
175
+ #
176
+ # @example Global configuration
177
+ # require 'lotus/view'
178
+ #
179
+ # class ApplicationLayout
180
+ # include Lotus::Layout
181
+ # end
182
+ #
183
+ # module Articles
184
+ # class Show
185
+ # include Lotus::View
186
+ # end
187
+ # end
188
+ #
189
+ # Lotus::View.layout = :application
190
+ # Articles::Show.layout # => nil
191
+ #
192
+ # Lotus::View.load!
193
+ # Articles::Show.layout # => :application
194
+ #
195
+ # @example Global configuration with custom layout
196
+ # require 'lotus/view'
197
+ #
198
+ # class ApplicationLayout
199
+ # include Lotus::Layout
200
+ # end
201
+ #
202
+ # class ArticlesLayout
203
+ # include Lotus::Layout
204
+ # end
205
+ #
206
+ # module Articles
207
+ # class Show
208
+ # include Lotus::View
209
+ # layout :articles
210
+ # end
211
+ # end
212
+ #
213
+ # Lotus::View.layout = :application
214
+ # Articles::Show.layout # => :articles
215
+ #
216
+ # Lotus::View.load!
217
+ # Articles::Show.layout # => :articles
218
+ def layout(value = nil)
219
+ if value
220
+ @layout = value
221
+ else
222
+ @layout
223
+ end
224
+ end
225
+
226
+ protected
227
+
228
+ # Loading mechanism hook.
229
+ #
230
+ # @api private
231
+ # @since 0.1.0
232
+ #
233
+ # @see Lotus::View.load!
234
+ def load!
235
+ super
236
+
237
+ views.each do |v|
238
+ v.root.freeze
239
+ v.format.freeze
240
+ v.template.freeze
241
+ v.layout(Rendering::LayoutFinder.new(v).find)
242
+ v.layout.freeze
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,50 @@
1
+ module Lotus
2
+ module View
3
+ # Inheriting mechanisms
4
+ #
5
+ # @since 0.1.0
6
+ module Inheritable
7
+ # Register a view subclass
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
11
+ #
12
+ # @example
13
+ # require 'lotus/view'
14
+ #
15
+ # class IndexView
16
+ # include Lotus::View
17
+ # end
18
+ #
19
+ # class JsonIndexView < IndexView
20
+ # end
21
+ def inherited(base)
22
+ subclasses.add base
23
+ end
24
+
25
+ # Set of registered subclasses
26
+ #
27
+ # @api private
28
+ # @since 0.1.0
29
+ def subclasses
30
+ @@subclasses ||= Set.new
31
+ end
32
+
33
+ protected
34
+ # Loading mechanism hook.
35
+ #
36
+ # @api private
37
+ # @since 0.1.0
38
+ #
39
+ # @see Lotus::View.load!
40
+ def load!
41
+ subclasses.freeze
42
+ views.freeze
43
+ end
44
+
45
+ def views
46
+ @@views ||= [ self ] + subclasses.to_a
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,104 @@
1
+ require 'lotus/utils/string'
2
+
3
+ module Lotus
4
+ module View
5
+ module Rendering
6
+ # Defines the logic to find a layout
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ #
11
+ # @see Lotus::Layout
12
+ class LayoutFinder
13
+ # Layout class name suffix
14
+ #
15
+ # @api private
16
+ # @since 0.1.0
17
+ SUFFIX = 'Layout'.freeze
18
+
19
+ # Find a layout from the given name.
20
+ #
21
+ # @param layout [Symbol,String,NilClass] layout name or nil if you want
22
+ # to fallback to the framework defaults (see `Lotus::View.layout`).
23
+ #
24
+ # @return [Lotus::Layout] the layout for the given name or
25
+ # `Lotus::View.layout`
26
+ #
27
+ # @api private
28
+ # @since 0.1.0
29
+ #
30
+ # @example With given name
31
+ # require 'lotus/view'
32
+ #
33
+ # Lotus::View::Rendering::LayoutFinder.find(:article) # =>
34
+ # ArticleLayout
35
+ #
36
+ # @example With nil
37
+ # require 'lotus/view'
38
+ #
39
+ # Lotus::View.layout # => :application
40
+ # Lotus::View::Rendering::LayoutFinder.find(nil) # =>
41
+ # ApplicationLayout
42
+ def self.find(layout)
43
+ case layout
44
+ when Symbol, String
45
+ class_name = "#{ Utils::String.new(layout).classify }#{ SUFFIX }"
46
+ Object.const_get(class_name)
47
+ when nil
48
+ Lotus::View.layout
49
+ end
50
+ end
51
+
52
+ # Initialize the finder
53
+ #
54
+ # @param view [Class, #layout]
55
+ #
56
+ # @api private
57
+ # @since 0.1.0
58
+ def initialize(view)
59
+ @view = view
60
+ end
61
+
62
+ # Find the layout for the view
63
+ #
64
+ # @return [Lotus::Layout] the layout associated to the view
65
+ #
66
+ # @see Lotus::View::Rendering::LayoutFinder.find
67
+ # @see Lotus::View::Rendering::LayoutFinder#initialize
68
+ #
69
+ # @api private
70
+ # @since 0.1.0
71
+ #
72
+ # @example With layout
73
+ # require 'lotus/view'
74
+ #
75
+ # module Articles
76
+ # class Show
77
+ # include Lotus::View
78
+ # layout :article
79
+ # end
80
+ # end
81
+ #
82
+ # Lotus::View::Rendering::LayoutFinder.new(Articles::Show) # =>
83
+ # ArticleLayout
84
+ #
85
+ # @example Without layout
86
+ # require 'lotus/view'
87
+ #
88
+ # module Dashboard
89
+ # class Index
90
+ # include Lotus::View
91
+ # end
92
+ # end
93
+ #
94
+ # Lotus::View.layout # => :application
95
+ #
96
+ # Lotus::View::Rendering::LayoutFinder.new(Dashboard::Index) # =>
97
+ # ApplicationLayout
98
+ def find
99
+ self.class.find(@view.layout)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,63 @@
1
+ require 'lotus/view/rendering/null_template'
2
+ require 'lotus/view/rendering/templates_finder'
3
+
4
+ module Lotus
5
+ module View
6
+ module Rendering
7
+ # Holds the references of all the registered layouts.
8
+ # As now the registry is unique at the level of the framework.
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ #
13
+ # @see Lotus::Layout::ClassMethods#registry
14
+ class LayoutRegistry < ::Hash
15
+ # Initialize the registry
16
+ #
17
+ # @param view [Class] the view
18
+ #
19
+ # @api private
20
+ # @since 0.1.0
21
+ def initialize(view)
22
+ super()
23
+
24
+ @view = view
25
+ prepare!
26
+ end
27
+
28
+ # Returns the layout for the given context.
29
+ #
30
+ # @param context [Hash] the rendering context
31
+ # @option context [Symbol] :format the requested format
32
+ #
33
+ # @return [Lotus::Layout, Lotus::View::Rendering::NullTemplate]
34
+ # the layout associated with the given context or a `NullTemplate` if
35
+ # it can't be found.
36
+ #
37
+ # @raise [Lotus::View::MissingFormatError] if the given context doesn't
38
+ # have the :format key
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def resolve(context)
43
+ fetch(format(context)) { NullTemplate.new }
44
+ end
45
+
46
+ protected
47
+ def prepare!
48
+ templates.each do |template|
49
+ merge! template.format => template
50
+ end
51
+ end
52
+
53
+ def templates
54
+ TemplatesFinder.new(@view).find
55
+ end
56
+
57
+ def format(context)
58
+ context.fetch(:format) { raise MissingFormatError }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end