sho 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 125faf4a77feba4430c913006c9a0c594032492c
4
+ data.tar.gz: 48b7aef74a0acd727c7cfa848fa95f932e22504a
5
+ SHA512:
6
+ metadata.gz: 8bf09de4470247c23f2cd1ab956d01996337c8e654337cd53fe20f8331bdab50027dd7c0d46ee0a40edf1baad9dc88bbec8550088f6c3a0293a4754119da5e29
7
+ data.tar.gz: a4f0689ca99551d70ef474b90d856321fc1894918ac280e9931b48cf57da8e1e46d551fec964b5fbfc689d38f16e6d467e8e93cf4f86c1f40db85ba53d03d626
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # Sho: post-framework view library
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/sho.svg)](http://badge.fury.io/rb/sho)
4
+ [![Build Status](https://travis-ci.org/zverok/sho.svg?branch=master)](https://travis-ci.org/zverok/sho)
5
+
6
+ **Sho** is an experimental **post-framework** Ruby view library. It is based on Tilt and meant to provide entire view layer for a web application (based on any framework or completely frameworkless).
7
+
8
+ <sup>"Sho?" ("Шо?") is a Kharkiv dialecticism, meaning "What?", typically in a slightly aggressive manner. It is chosen as a library name because it is close to "show" in a written and spoken form (and also because "What?" is expected typical reaction to the library).</sup>
9
+
10
+ **Post-framework** means Sho behaves as a decent _library_, neither implying nor forcing _any_ kind of conventions for your code layout and structure.
11
+
12
+ Currently, if you want your app architecture not to follow strict framework's _frame_, you can take ROM or Sequel for _data layer_; Sinatra, Roda, or Grape for _routing layer_; but for _view layer_, the situation seems to be different. Typical "views" part of the application (say, with Rails, Hanami, Padrino) is bound to a lot of conventions (like "it will look for the corresponding template in that folders") and global configuration, and tightly coupled with routes/controllers ("if you want data from controller to be passed to view, you mark it so an so").
13
+
14
+ **Sho** is an experiment to provide view layer that is "just Ruby" (= follows the regular intuitions of _Ruby_ programmer, not introducing some hidden conventions that are not deductible from the code) and reuses regular Ruby concepts for code sharing, parameters passing and flow structuring instead of introducing its own concepts like "helpers", "exposures", "locals" (completely unlike local variables!) and so on.
15
+
16
+ ## Basic synopsis
17
+
18
+ ```ruby
19
+ # in any class you want
20
+ include Sho
21
+
22
+ sho.template :name, 'path/to/template.erb', :param1, param2: default_value
23
+ ```
24
+
25
+ This creates instance method with a signature¹ `YourClass#name(param1:, param2: default_value)`, which, when called, renders the template from `path/to/template.erb`.
26
+
27
+ <sup>¹Due to metaprogramming limitations, real signature of method would be `name(**params)` and check of mandatory params and assignment of defaults is performed by Sho.</sup>
28
+
29
+ You can think about the template as a _method body_, which immediately answers a lot of questions:
30
+
31
+ * **What context the template is evaluated in?** Like any method: in context of the instance of the class, where the method is defined.
32
+ * **What names are available in the template?** The same as in any methods: parameters, and other methods/variables of the instance.
33
+ * **How do I do share "helper" code with several templates?** Just in a regular Ruby: extract it to a module, include the module in several classes with views. Or make the base class and inherit from it. Or use any other code sharing technique you are fond of.
34
+ * **How do I do "partials" (render one template from another)?** The same as when you want to call one method from another one: just call it.
35
+ ```ruby
36
+ # In `user.rb`
37
+ sho.template :render, 'user.slim'
38
+ # That would be "partial":
39
+ sho.template :status_with_popup, 'user/_status_with_popup.slim'
40
+ ```
41
+ ```slim
42
+ / In `user.slim` (think of it as a body for `User#render` method):
43
+ p
44
+ span.name = name
45
+ / Call of a "partial":
46
+ span.status = status_with_popup
47
+ ```
48
+ * **How do I test it? How do I set all the context for testing?** Just as with regular method: just create an instance, and call the method, and test the result.
49
+ * **But where do I put this method?** Wherever you wish! Sho does NOT insist on any particular architecture or code layout, which means you can experiment and evaluate several options, like:
50
+ * embed rendering in controller/Sinatra app (or even model, if you want to be really naughty today!) for the very first 30-lines-long prototype, then move it elsewhere (like "Extract Method" refactoring pattern, you know?)
51
+ * embed rendering in your service (operation) objects, so
52
+ * make Users::List class with `#html`, `#atom` and `#json` methods and use it like `Users::List.new(scope).send(request.format)` or `User::List.send(request.format, scope)`
53
+ * make `Trailblazer::Cells`-like one-class-per-template objects to call them like `Users::HtmlList.new(scope).()`
54
+ * ...switch between several of the approaches, or even combine them in the same app!
55
+
56
+ ## Implementation details
57
+
58
+ **Where should I store the templates?**
59
+
60
+ Sho doesn't have any global configuration for "templates folder", neither convention for "templates are in `app/views/<current_class_name>`" or something like that. `template` method just looks for templates relative to current working folder (`Dir.pwd`). As it could be tiresome to write `app/views/blah/blah/blah/blah.slim` for each and every method, there is `sho.base_folder = ` class-level setting:
61
+
62
+ ```ruby
63
+ # Before
64
+ sho.template :profile, 'app/views/users/profile.slim'
65
+ sho.template :icon, 'app/views/users/icon.slim'
66
+
67
+ # After
68
+ sho.base_folder = 'app/views/users'
69
+ sho.template :profile, 'profile.slim'
70
+ sho.template :icon, 'icon.slim'
71
+
72
+ # If all of your classes and templates are in the same `app/view`, further shortcutting is
73
+ # your own responsibility, like:
74
+
75
+ sho.base_folder = VIEWS_BASE + '/users'
76
+ ```
77
+
78
+ Another interesting approach that is made easy by Sho:
79
+ ```ruby
80
+ # In app/view_models/users.rb
81
+
82
+ # It is like require_relative, template should be stored at
83
+ # app/view_models/users/profile.slim
84
+ sho.template_relative :profile, 'users/profile.slim'
85
+ ```
86
+
87
+ The idea is: as `ViewModels::Users` have `profile.slim` as a `#profile` method body, it is this class' implementation details, so, there is no point to store it in a completely different folder.
88
+
89
+ **What about layouts?**
90
+
91
+ **Sho** supports concept of layout with `:_layout` param. It accepts method name, and supposes this method will call `yield` at some point:
92
+
93
+ ```ruby
94
+ # in app/view_models/users.rb
95
+ sho.template_relative :list, 'users/list.slim', _layout: :main_layout
96
+ sho.template :main_layout, 'app/views/main_layout.slim'
97
+ ```
98
+
99
+ Sharing of the layout between several classes could be done in the same way as sharing of any other methods: extract it to a common module, and include wherever you like.
100
+
101
+ **Small-scale usage of the library**
102
+
103
+ As Sho is a _library_, not a framework, it doesn't require you to switch to Sho-only code immediately and completely. You can try it in some parts of your system, or just in one class. One useful idea is to use it in decorators (like [draper](https://github.com/drapergem/draper)), and Sho provides `inline_template` for this kind of usage:
104
+
105
+ ```ruby
106
+ class RatingDecorator < Draper::Decorator
107
+ # ...
108
+
109
+ # before:
110
+ def row
111
+ h.content_tag(:tr,
112
+ h.safe_join([
113
+ h.content_tag(:th, "Rated by #{user.name}"),
114
+ h.content_tag(:td, stars),
115
+ h.content_tag(:td, rated_at)
116
+ ]),
117
+ class: 'rating'
118
+ )
119
+ end
120
+
121
+ # after:
122
+ include Sho
123
+
124
+ sho.inline_template :row,
125
+ slim: <<~SLIM
126
+ tr.rating
127
+ th
128
+ | Rated by
129
+ = user.name
130
+ td = stars
131
+ td = rated_at
132
+ SLIM
133
+ end
134
+ ```
135
+
136
+ **Template caching**
137
+
138
+ Sho creates Tilt templates at a moment of the method definition. This seems to lead to most natural behavior: the templates are found and cached at a moment of code loading/reloading (whatever reloader you use).
139
+
140
+ ## Library status
141
+
142
+ It is fresh and experimental. Tested, documented and stuff, but still not extensively used in production. Nothing guaranteed, but I'll be happy to have at least a meaningful discussion started.
143
+
144
+ ## Author
145
+
146
+ [Victor Shepelev aka @zverok](https://zverok.github.io)
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sho
4
+ # @private
5
+ class ArgumentValidator
6
+ def initialize(*mandatory, **optional)
7
+ mandatory.all? { |m| m.is_a?(Symbol) } or
8
+ fail ArgumentError, 'Mandatory arguments should be send as array of symbols'
9
+ @mandatory = mandatory
10
+ @optional = optional
11
+ end
12
+
13
+ def call(**params)
14
+ guard_missing!(params)
15
+ guard_unknown!(params)
16
+ params.merge(@optional.reject { |key,| params.key?(key) })
17
+ end
18
+
19
+ private
20
+
21
+ def guard_missing!(**params)
22
+ (@mandatory - params.keys).tap do |missing|
23
+ missing.empty? or fail ArgumentError, "missing keywords: #{missing.join(', ')}"
24
+ end
25
+ end
26
+
27
+ def guard_unknown!(**params)
28
+ (params.keys - @mandatory - @optional.keys).tap do |unknown|
29
+ unknown.empty? or fail ArgumentError, "unknown keywords: #{unknown.join(', ')}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sho
4
+ # rubocop:disable Lint/UnderscorePrefixedVariableName
5
+
6
+ # Main Sho object providing rendering method creation API.
7
+ #
8
+ # There are three ways to create rendering methods:
9
+ #
10
+ # * {#template}: template is looked up relative to main folder, or {#base_folder};
11
+ # * {#template_relative}: template is looked up relative to current class' folder;
12
+ # * {#inline_template}: template is provided inline as a Ruby string/heredoc.
13
+ #
14
+ # @example
15
+ # class AnyClass
16
+ # include Sho
17
+ #
18
+ # # `sho` returns an instance of Configurator
19
+ # sho.template :rendering_method_name, 'path/to/template.slim', :param1, param2: default_value
20
+ # end
21
+ #
22
+ class Configurator
23
+ # @private
24
+ attr_reader :host
25
+
26
+ # @return [String, nil] folder to look templates at for {#template} method. `nil` by default,
27
+ # meaning application's current folder (`Dir.pwd`).
28
+ attr_accessor :base_folder
29
+
30
+ # @private
31
+ def initialize(host)
32
+ @host = host
33
+ end
34
+
35
+ # Generates instance method named `name` in a host module, which renders template from
36
+ # `template`.
37
+ # Instance of the host class is passed as a template scope on rendering.
38
+ #
39
+ # Template is looked up relative to application's main folder, or {#base_folder}.
40
+ #
41
+ # @example
42
+ # # generates method with signature #profile()
43
+ # sho.template :profile, 'app/views/users/profile.slim'
44
+ #
45
+ # # generates method with signature #profile(context:, detailed: false)
46
+ # # `context` and `detailed` vairables are accessible inside template.
47
+ # sho.template :profile, 'app/views/users/profile.slim', :context, detailed: false
48
+ #
49
+ # @param name [Symbol] name of method to generate;
50
+ # @param template [String] path to template to render;
51
+ # @param _layout [Symbol, nil] name of method which provides layout (wraps results of current
52
+ # method);
53
+ # @param mandatory [Array<Symbol>] list of mandatory params;
54
+ # @param optional [Hash{Symbol => Object}] list of optional params and their default values
55
+ def template(name, template, *mandatory, _layout: nil, **optional)
56
+ tpl = Tilt.new(File.expand_path(template, base_folder || Dir.pwd))
57
+ define_template_method(name, tpl, mandatory, optional, _layout)
58
+ end
59
+
60
+ # Like {#template}, but looks up template relative to host module path. Allows to structure
61
+ # views like:
62
+ #
63
+ # ```
64
+ # app/
65
+ # +- view_models/
66
+ # +- users.rb # calls sho.template_relative :profile, 'users/profile.slim'
67
+ # +- users/
68
+ # +- profile.slim
69
+ # ```
70
+ #
71
+ # @param name [Symbol] name of method to generate;
72
+ # @param template [String] path to template to render;
73
+ # @param _layout [Symbol, nil] name of method which provides layout (wraps results of current
74
+ # method);
75
+ # @param mandatory [Array<Symbol>] list of mandatory params;
76
+ # @param optional [Hash{Symbol => Object}] list of optional params and their default values
77
+ def template_relative(name, template, *mandatory, _layout: nil, **optional)
78
+ base = File.dirname(caller(1..1).first.split(':').first)
79
+ tpl = Tilt.new(File.expand_path(template, base))
80
+
81
+ define_template_method(name, tpl, mandatory, optional, _layout)
82
+ end
83
+
84
+ # Inline rendering method definition, useful in decorators and other contexts with small
85
+ # templates.
86
+ #
87
+ # @example
88
+ # sho.inline_template :badge,
89
+ # slim: <<~SLIM
90
+ # span.badge
91
+ # span.name = user.name
92
+ # i.role(class: user.role)
93
+ # SLIM
94
+ #
95
+ # @param name [Symbol] name of method to generate;
96
+ # @param _layout [Symbol, nil] name of method which provides layout (wraps results of current
97
+ # method);
98
+ # @param mandatory [Array<Symbol>] list of mandatory params;
99
+ # @param options [Hash{Symbol => Object}] list of optional params and their default values +
100
+ # template to render (passed in a key named `slim:` or `erb:` or `haml:`, and so on).
101
+ def template_inline(name, *mandatory, _layout: nil, **options)
102
+ kind, template = options.detect { |key,| Tilt.registered?(key.to_s) }
103
+ template or fail ArgumentError, "No known templates found in #{options.keys}"
104
+ optional = options.reject { |key,| key == kind }
105
+ tpl = Tilt.default_mapping[kind].new { template }
106
+
107
+ define_template_method(name, tpl, mandatory, optional, _layout)
108
+ end
109
+
110
+ alias inline_template template_inline
111
+
112
+ private
113
+
114
+ def define_template_method(name, tilt, mandatory, optional, layout)
115
+ arguments = ArgumentValidator.new(*mandatory, **optional)
116
+ @host.__send__(:define_method, name) do |**locals|
117
+ locals = arguments.call(**locals)
118
+ if layout
119
+ __send__(layout) { tilt.render(self, **locals) }
120
+ else
121
+ tilt.render(self, **locals)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ # rubocop:enable Lint/UnderscorePrefixedVariableName
127
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sho
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+ PRE = nil
8
+ VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
9
+ end
data/lib/sho.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tilt'
4
+
5
+ # Sho is a small, non-framework view library based on Tilt.
6
+ #
7
+ # `sho` object in an example below is an instance of {Sho::Configurator}, look at its docs to
8
+ # understand how to define rendering methods.
9
+ #
10
+ # @example
11
+ # class AnyClass
12
+ # include Sho
13
+ #
14
+ # sho.template :rendering_method_name, 'path/to/template.slim', :param1, param2: default_value
15
+ # end
16
+ #
17
+ # # with instance of AnyClass:
18
+ # object.rendering_method_name(param1: 'foo', param2: 'bar') # => template.slim rendered
19
+ #
20
+ module Sho
21
+ # Adds `#sho` method (access to instance of {Sho::Configurator}) to class/module `Sho` is
22
+ # included into.
23
+ def self.included(mod)
24
+ mod.define_singleton_method(:sho) {
25
+ @__sho_configurator__ ||= Configurator.new(mod)
26
+ }
27
+ end
28
+ end
29
+
30
+ require_relative 'sho/argument_validator'
31
+ require_relative 'sho/configurator'
metadata ADDED
@@ -0,0 +1,246 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sho
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Victor Shepelev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tilt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: redcarpet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: github-markup
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard-junk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.7.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.7.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-its
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: saharspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: fakefs
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: slim
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop-rspec
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rake
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubygems-tasks
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ description: |2
210
+ Post-framework Ruby view library. It is based on Tilt and meant to provide entire
211
+ view layer for a web application (based on any framework or completely frameworkless).
212
+ email: zverok.offline@gmail.com
213
+ executables: []
214
+ extensions: []
215
+ extra_rdoc_files: []
216
+ files:
217
+ - README.md
218
+ - lib/sho.rb
219
+ - lib/sho/argument_validator.rb
220
+ - lib/sho/configurator.rb
221
+ - lib/sho/version.rb
222
+ homepage: https://github.com/zverok/sho
223
+ licenses:
224
+ - MIT
225
+ metadata: {}
226
+ post_install_message:
227
+ rdoc_options: []
228
+ require_paths:
229
+ - lib
230
+ required_ruby_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: 2.1.0
235
+ required_rubygems_version: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: '0'
240
+ requirements: []
241
+ rubyforge_project:
242
+ rubygems_version: 2.6.14
243
+ signing_key:
244
+ specification_version: 4
245
+ summary: Experimental post-framework view library
246
+ test_files: []