rocketio-views 0.4.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 12d12aece5cc59444351677460242a3b0315b8e1
4
+ data.tar.gz: 761985a5b8353827fac8895f685172e7c73497c8
5
+ SHA512:
6
+ metadata.gz: ae3365ec7bc3f4f8088f6985efdf4d49c155099d894a75a16d6e5149267fd0615af23515df7efd723b3b36592770b5d32c0f31cff7456c7de5333c5107483c4e
7
+ data.tar.gz: 94be982fc21bae4171a79660fa211a29461b1836947183d894de4e303097e512f5acbc7c8f75572301ff31587203fc1313daaa0fc68b89e21aed365b41832bb2
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
11
+ .pryrc
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rocketio.gemspec
4
+ gemspec
@@ -0,0 +1,3 @@
1
+ # RocketIO Views
2
+
3
+ #### View layer for RocketIO
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'tokyo'
3
+
4
+ task :test do
5
+ Tokyo.run
6
+ end
7
+
8
+ task default: :test
@@ -0,0 +1,50 @@
1
+ # core
2
+ require 'forwardable'
3
+
4
+ # gems
5
+ require 'rocketio'
6
+ require 'tilt'
7
+
8
+ module RocketIO
9
+
10
+ FOUND_TEMPLATES = {}
11
+ READ_TEMPLATES = {}
12
+ COMPILED_TEMPLATES = {}
13
+
14
+ ENGINE_CONST_FORMAT = '%sTemplate'.freeze
15
+ TEMPLATE_PATH_FORMAT = '%s/%s.%s'.freeze
16
+
17
+ DEFAULT_ENGINE = [Tilt::ERBTemplate, [].freeze].freeze
18
+
19
+ INHERITABLE_SETUPS.concat([
20
+ :engine,
21
+ :layout,
22
+ :layouts,
23
+ :templates,
24
+ :template_vars,
25
+ ])
26
+
27
+ class TemplateError < RuntimeError; end
28
+ class LayoutError < RuntimeError; end
29
+
30
+ # building a constant name for given engine name.
31
+ # if a class given, return it as is.
32
+ #
33
+ # @example
34
+ # engine_class(:Slim) #=> :SlimTemplate
35
+ #
36
+ # @param engine name
37
+ # @return [Symbol, Class]
38
+ #
39
+ def engine_class engine
40
+ return engine if engine.is_a?(Class)
41
+ (RocketIO::ENGINE_CONST_FORMAT % engine).to_sym
42
+ end
43
+
44
+ def engine_const engine
45
+ return engine if engine.is_a?(Class)
46
+ ::Tilt.const_get(engine)
47
+ end
48
+ end
49
+
50
+ require 'rocketio-views/controller'
@@ -0,0 +1,170 @@
1
+ module RocketIO
2
+ class Controller
3
+ extend Forwardable
4
+
5
+ def_delegators RocketIO, :engine_const, :engine_class
6
+
7
+ # if called without arguments render a template with lowercased name of current request method, e.g. get for GET, post for POST etc.
8
+ # at first it will look between defined templates.
9
+ # then it will search a file.
10
+ # it will try each extension the effective engine has registered, e.g. .erb, .rhtml for ERB.
11
+ # it will search in the folder the controller was defined in(NOT in path_to_templates, which is used for defined templates only).
12
+ # so put each controller in a separate folder to avoid templates clash.
13
+ #
14
+ # if no file found a TemplateError will be raised.
15
+ # if a block given it will use the the string returned by the block as template
16
+ # and wont search for defined nor file templates.
17
+ #
18
+ # by default ERB engine will be used (@see engine).
19
+ #
20
+ # for layout it will take one given through options or one defined at class level.
21
+ # if none given, it will render without layout.
22
+ # to use a layout path_to_layouts should be defined at class level.
23
+ #
24
+ # by default it will use current instance as scope.
25
+ # to render in a isolated scope, set it via :scope option
26
+ #
27
+ # to pass some local variables use :locals option
28
+ #
29
+ # @example render ./get.erb without layout
30
+ # class Pages < RocketIO::Controller
31
+ #
32
+ # def get
33
+ # render
34
+ # end
35
+ # end
36
+ #
37
+ # @example render ./get.erb with :master layout
38
+ # class Pages < RocketIO::Controller
39
+ # layout :master
40
+ #
41
+ # def get
42
+ # render
43
+ # end
44
+ # end
45
+ #
46
+ # @example render ./get.erb with explicit :master layout
47
+ # class Pages < RocketIO::Controller
48
+ #
49
+ # def get
50
+ # render(layout: :master)
51
+ # end
52
+ # end
53
+ #
54
+ # @example render within isolated scope
55
+ # class Pages < RocketIO::Controller
56
+ #
57
+ # def get
58
+ # render(scope: Object.new)
59
+ # end
60
+ # end
61
+ #
62
+ # @example render with custom locals
63
+ # class Pages < RocketIO::Controller
64
+ #
65
+ # def get
66
+ # render(locals: {x: 'y'})
67
+ # end
68
+ # end
69
+ #
70
+ def render template = nil, opts = {}
71
+ opts, template = template, nil if template.is_a?(::Hash)
72
+ engine, engine_opts = resolve_engine(opts)
73
+ template = block_given? ? yield : resolve_template(template, engine)
74
+ scope = opts.fetch(:scope, self)
75
+ locals = template_vars.merge(opts.fetch(:locals, RocketIO::EMPTY_HASH)).freeze
76
+ layout = opts.fetch(:layout, self.layout)
77
+ template = compile_template(template, engine, engine_opts).render(scope, locals)
78
+ layout ? render_layout(layout, opts) {template} : template
79
+ end
80
+
81
+ # render a template that yields the given block.
82
+ # that's it, a layout is a template that yields given string.
83
+ #
84
+ # layout can be specified two ways:
85
+ # - as layout name
86
+ # - as string
87
+ #
88
+ # if both given a ArgumentError error raised.
89
+ # if :template option given, no layout lookups will occur.
90
+ #
91
+ # otherwise...
92
+ # if no layout name given, it will use the one set at class level.
93
+ # if no layout set at class level and no layout given, it will raise a RuntimeError.
94
+ #
95
+ # then it will search for given layout between defined ones.
96
+ # if none found, it will search a file in `path_to_layouts` folder.
97
+ # it will try each extension registered with effective engine.
98
+ # if no file found it will raise a TemplateError.
99
+ #
100
+ # block is required and should return the string to be yielded.
101
+ #
102
+ def render_layout template = nil, opts = {}, &block
103
+ template && opts[:template] && raise(ArgumentError, 'Both layout name and :template option given. Please use either one.')
104
+
105
+ opts, template = template, nil if template.is_a?(::Hash)
106
+ engine, engine_opts = resolve_engine(opts)
107
+ template = if template
108
+ resolve_layout(template, engine)
109
+ else
110
+ opts[:template] || begin
111
+ self.layout || raise(RocketIO::LayoutError, 'No default layout set and no explicit layout given')
112
+ resolve_layout(self.layout, engine)
113
+ end
114
+ end
115
+
116
+ scope = opts.fetch(:scope, self)
117
+ locals = template_vars.merge(opts.fetch(:locals, RocketIO::EMPTY_HASH)).freeze
118
+ compile_template(template, engine, engine_opts)
119
+ .render(scope, locals, &(block || RocketIO::EMPTY_STRING_PROC))
120
+ end
121
+
122
+ private
123
+ def resolve_template template, engine
124
+ template ||= requested_method
125
+ return __send__(templates[template]) if templates[template]
126
+ read_template(find_template(dirname, template, engine))
127
+ end
128
+
129
+ def resolve_layout layout, engine
130
+ return __send__(layouts[layout]) if layouts[layout]
131
+ read_template(find_template(dirname, layout, engine))
132
+ end
133
+
134
+ def resolve_engine opts = nil
135
+ return engine_const(engine_class(opts[:engine])) if opts && opts[:engine]
136
+ [engine_const(engine[0]), engine[1]]
137
+ end
138
+
139
+ def read_template file
140
+ RocketIO::READ_TEMPLATES[[file, ::File.mtime(file).to_i]] ||= ::File.read(file)
141
+ end
142
+
143
+ def find_template path, template, engine
144
+ RocketIO::FOUND_TEMPLATES[[path, template, engine]] ||= begin
145
+ extensions = ::Tilt.default_mapping.extensions_for(engine)
146
+ file = nil
147
+ extensions.each do |ext|
148
+ try = RocketIO::TEMPLATE_PATH_FORMAT % [path, template, ext]
149
+ next unless File.exists?(try)
150
+ file = try
151
+ break
152
+ end
153
+ file || raise(RocketIO::TemplateError, '"%s" template not found in "%s".
154
+ Tried extensions: %s' % [template, path, extensions.join(', ')])
155
+ end
156
+ end
157
+
158
+ def compile_template template, engine, engine_opts = nil
159
+ RocketIO::COMPILED_TEMPLATES[[template.hash, engine, engine_opts]] ||= begin
160
+ engine.new(0, *engine_opts) { template }
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ require 'rocketio-views/engine'
167
+ require 'rocketio-views/layout'
168
+ require 'rocketio-views/layouts'
169
+ require 'rocketio-views/templates'
170
+ require 'rocketio-views/template_vars'
@@ -0,0 +1,76 @@
1
+ module RocketIO
2
+ class Controller
3
+
4
+ # if no engine set, templates will be rendered using ERB engine.
5
+ # any engine supported by [Tilt](github.com/rtomayko/tilt) can be used.
6
+ # to set engine use symbolized constant name, e.g. :Slim, :Haml
7
+ # engine name is Case Sensitive and there should be a Tilt::{ENGINE}Template class defined
8
+ # e.g. `engine :Slim` will look for Tilt::SlimTemplate
9
+ # and `engine RDiscount` will look for Tilt::RDiscountTemplate
10
+ #
11
+ # @note if a block given it will be executed at instance level
12
+ # and result used for engine. To have any options passed at engine initialization
13
+ # the block should return an array having engine as first element
14
+ # and options as consequent elements.
15
+ #
16
+ # @note if given block returns no engine, inherited engine will be used
17
+ #
18
+ # @example use static engine
19
+ # engine :Slim
20
+ #
21
+ # @example use static engine with options
22
+ # engine :Slim, pretty_print: true
23
+ #
24
+ # @example use dynamically set engine
25
+ # engine do
26
+ # some_condition ? :SomeEngine : AnotherEngine
27
+ # end
28
+ #
29
+ # @example use dynamically set engine with options
30
+ # engine do
31
+ # some_condition ? [:SomeEngine, :opt1, :opt2] : AnotherEngine
32
+ # end
33
+ #
34
+ # @example Search will use ERB when requested by a bot and Slim otherwise
35
+ #
36
+ # class BaseController < RocketIO::Controller
37
+ # engine :Slim
38
+ # end
39
+ #
40
+ # class Search < BaseController
41
+ # engine do
42
+ # if request.user_agent =~ /i'm a bot/
43
+ # # requested by a bot, using ERB
44
+ # return :ERB
45
+ # end
46
+ # # requested by a user, returning no engine for inherited engine to be used
47
+ # end
48
+ # end
49
+ #
50
+ # @param engine engine name.
51
+ # @param *engine_options any arguments to be passed at engine initialization
52
+ # @param block to be executed at instance level
53
+ #
54
+ def self.engine engine = nil, *engine_options, &block
55
+ @__engine__ = block || [RocketIO.engine_class(engine), engine_options.freeze].freeze
56
+ define_engine_methods
57
+ end
58
+
59
+ def self.define_engine_methods source = self
60
+ return unless engine = source.instance_variable_get(:@__engine__)
61
+ if Proc === engine
62
+ selfengine = allocate.engine
63
+ api.delete define_method(:__rocketio_engine__, &engine)
64
+ api.delete define_method(:engine) {
65
+ engine, *engine_options = __rocketio_engine__
66
+ return selfengine unless engine
67
+ [RocketIO.engine_class(engine), engine_options.freeze].freeze
68
+ }
69
+ else
70
+ api.delete define_method(:engine) {engine}
71
+ end
72
+ end
73
+
74
+ def engine; RocketIO::DEFAULT_ENGINE end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ module RocketIO
2
+ class Controller
3
+
4
+ # by default templates will be rendered without layout.
5
+ # to make them render inside a layout use `layout :layout_name` at class level.
6
+ # to use a layout it should be defined at first (@see define_layout)
7
+ #
8
+ # @note to disable layout set it to false: `layout false`
9
+ #
10
+ # @param layout name of a defined layout
11
+ #
12
+ def self.layout layout
13
+ @__layout__ = layout
14
+ define_layout_methods
15
+ end
16
+
17
+ def self.define_layout_methods source = self
18
+ return unless source.instance_variables.include?(:@__layout__)
19
+ layout = source.instance_variable_get(:@__layout__)
20
+ api.delete define_method(:layout) {layout}
21
+ end
22
+
23
+ # by default no layout used, so this method returns nil.
24
+ # controllers that uses a layout will override this method.
25
+ def layout; end
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ module RocketIO
2
+ class Controller
3
+
4
+ # if only name given it will search for a file with same name in controller's dirname.
5
+ #
6
+ # if file name differs from layout name pass it as :file option.
7
+ # file path should be relative to controller's dirname.
8
+ # also a block accepted for :file option. the block will be executed at controllers's instance level
9
+ # and should return path to layout file.
10
+ # file name should NOT include extension.
11
+ #
12
+ # if a block given NO file will be searched and returned value will be used as layout.
13
+ #
14
+ # @note files will be searched relative to controller's dirname,
15
+ # that's it, the folder controller was defined in and operates from.
16
+ #
17
+ # @note when searching for file multiple extensions will be tried,
18
+ # that's it, all extensions controller's engine actually supports.
19
+ #
20
+ # @note controllers that inherits named layouts will always search for files in own dirname.
21
+ # controllers that inherits :file layouts will search files in the original controller's dirname.
22
+ #
23
+ # @example define :master layout.
24
+ # ./master.erb file will be used
25
+ #
26
+ # define_layout :master
27
+ #
28
+ # @example define :master layout.
29
+ # ../layouts/master.erb file will be used
30
+ #
31
+ # define_layout :master, file: '../layouts/master'
32
+ #
33
+ # @example define :master layout.
34
+ # ./admin file to be used when user logged in and ./user otherwise
35
+ #
36
+ # define_layout :master, file: -> {user? ? 'admin' : 'user'}
37
+ #
38
+ # @example define :master layout using a block that returns the layout string.
39
+ # no file will be used.
40
+ #
41
+ # define_layout(:master) do
42
+ # layout = Layouts.find_by(id: params[:layout_id]) || halt(400, 'Template not found')
43
+ # layout.source
44
+ # end
45
+ #
46
+ # @param name
47
+ # @param file
48
+ # @param block
49
+ #
50
+ def self.define_layout name, file: nil, &block
51
+ file && block && raise(::ArgumentError, 'both file and block given, please use either one')
52
+ (@__layouts__ ||= {})[name.to_sym] = {block: block, root: dirname, file: file, name: name}.freeze
53
+ define_layouts_methods
54
+ end
55
+
56
+ def self.define_layouts_methods source = self
57
+ return unless source.instance_variables.include?(:@__layouts__)
58
+ layouts = source.instance_variable_get(:@__layouts__).each_with_object(allocate.layouts.dup) do |(name,setup),o|
59
+ o[name] = :"__#{name}_layout__"
60
+ if setup[:block]
61
+ # block given, do not search for file, use returned value instead
62
+ api.delete define_method(o[name], &setup[:block])
63
+ elsif setup[:file]
64
+ # file given, search the file in original controller dirname
65
+ meth_name = :"__#{name}_layout_file__"
66
+ meth_proc = setup[:file].is_a?(::Proc) ? setup[:file] : -> {setup[:file]}
67
+ api.delete define_method(meth_name, &meth_proc)
68
+ api.delete define_method(o[name]) {
69
+ engine, * = resolve_engine
70
+ read_template(find_template(setup[:root], __send__(meth_name), engine))
71
+ }
72
+ else
73
+ # only name given, search for a file with same name in controller's dirname
74
+ api.delete define_method(o[name]) {
75
+ engine, * = resolve_engine
76
+ read_template(find_template(self.dirname, setup[:name], engine))
77
+ }
78
+ end
79
+ end.freeze
80
+ api.delete define_method(:layouts) {layouts}
81
+ end
82
+
83
+ def layouts; RocketIO::EMPTY_HASH end
84
+ end
85
+ end