rocketio-views 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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