lotus-view 0.0.0 → 0.1.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.
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