snails 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fb169efa64f0924773fdb6ecb80d4ee3d97eedf7
4
+ data.tar.gz: 55a634a3eca80533eb19ff052cfde946bff87ee4
5
+ SHA512:
6
+ metadata.gz: 44dd9f9eb766b6f422f7ba845d38a4f447461787d4eb4beaf5799d7db6c04eb90b34a59e75e6ae39c7741d3bce80dd3022d6002c49aca7da58733da8b98f0685
7
+ data.tar.gz: 74f232515e3c4f7c3e8d787cf09c54100fdefab6eab84e113941b0729d23ab71c1877439cdfdbeedad96a4ea8910c88a2dce37c9a6e414cc535e87ef8a42006d
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg
2
+ test
3
+ example/Gemfile.lock
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/example/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'puma'
4
+ gem 'snails', path: '..'
5
+ gem 'rake'
data/example/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/setup'
2
+ require 'snails'
3
+
4
+ class MyApp < Snails::App
5
+ # register Snails::Assets
6
+ # register Snails::Database
7
+ end
8
+
9
+ require 'snails/tasks'
data/example/config.ru ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler/setup'
2
+ require 'snails'
3
+
4
+ class MyApp < Snails::App
5
+ set :session_secret, 'foobar'
6
+
7
+ get '/' do
8
+ 'Hello world!'
9
+ end
10
+ end
11
+
12
+ run MyApp
@@ -0,0 +1,20 @@
1
+ require 'snails'
2
+
3
+ if defined?(Sinatra::ActiveRecordHelper)
4
+ require 'sinatra/activerecord/rake'
5
+ end
6
+
7
+ namespace :assets do
8
+
9
+ desc 'Precompiles assets'
10
+ task :precompile do
11
+ if Snails.apps.empty?
12
+ puts "No apps defined."
13
+ else
14
+ Snails.apps.each do |app|
15
+ Snails::Assets::Tasks.precompile_for(app)
16
+ end
17
+ end
18
+ end
19
+
20
+ end
data/lib/snails.rb ADDED
@@ -0,0 +1,418 @@
1
+ %w(
2
+ logger
3
+ active_support/string_inquirer
4
+ active_support/core_ext/hash
5
+ sinatra/base
6
+ sinatra/content_for
7
+ sinatra/flash
8
+ ).each { |lib| require lib }
9
+
10
+ module Snails
11
+ def self.env
12
+ @env ||= ActiveSupport::StringInquirer.new(ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development')
13
+ end
14
+
15
+ def self.apps
16
+ @apps ||= []
17
+ end
18
+
19
+ def self.app
20
+ puts "Warning: There's more than one Snail app defined!" if @apps.count > 1
21
+ @apps.first
22
+ end
23
+ end
24
+
25
+ module Snails
26
+
27
+ module RequiredParams
28
+ def requires!(req, hash = params)
29
+ if req.is_a?(Hash)
30
+ req.each do |k, vals|
31
+ if vals.is_a?(Array) or vals.is_a?(Hash)
32
+ halt(400, "Missing: #{k} in #{hash}") if hash[k].nil?
33
+ requires!(vals, hash[k])
34
+ else
35
+ requires!(k, hash)
36
+ end
37
+ end
38
+ elsif req.nil? or (req.is_a?(Symbol) and hash[req].nil?) \
39
+ or (req.is_a?(Array) and req.any? { |p| hash[p].nil? })
40
+ halt(400, "Required parameters: #{req} (in #{hash})")
41
+ end
42
+ end
43
+ end
44
+
45
+ class App < Sinatra::Base
46
+
47
+ def self.inherited(base)
48
+ Snails.apps << base
49
+ super
50
+ end
51
+
52
+ LOGGER = Logger.new(File.exist?("#{Dir.pwd}/log") ? "#{Dir.pwd}/log/#{Snails.env}.log" : nil)
53
+
54
+ cwd = Pathname.new(Dir.pwd) # settings.root
55
+ set :protection, except: :frame_options
56
+ set :views, cwd.join('lib', 'views')
57
+ set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex }
58
+ enable :sessions
59
+ enable :method_override
60
+ enable :logging
61
+
62
+ register Sinatra::Flash
63
+ use Rack::CommonLogger, LOGGER
64
+ use Rack::Static, urls: %w(/css /img /js /files /fonts favicon.ico), root: 'public'
65
+
66
+ configure :production, :staging do
67
+ set :raise_errors, true
68
+ set :dump_errors, false
69
+ end
70
+
71
+ configure :development do
72
+ set :raise_errors, true
73
+ set :show_exceptions, true
74
+ set :log_level, Logger::DEBUG
75
+ end
76
+
77
+ helpers do
78
+ include RequiredParams
79
+ include Sinatra::ContentFor
80
+ def logger; LOGGER; end
81
+ end
82
+
83
+ error do
84
+ err = request.env['sinatra.error']
85
+ logger.error err.message
86
+ logger.error err.backtrace.first(3).join("\n")
87
+ halt(500, err.message)
88
+ end
89
+
90
+ not_found do
91
+ show_error(404)
92
+ end
93
+
94
+ protected
95
+
96
+ def deliver(data, code = 200, format = :json)
97
+ status(code)
98
+ content_type(format)
99
+ data.public_send("to_#{format}")
100
+ end
101
+
102
+ def show_error(code)
103
+ erb :"errors/#{code}", :layout => false
104
+ end
105
+
106
+ end
107
+
108
+ module Database
109
+
110
+ def self.registered(app)
111
+ require 'sinatra/activerecord'
112
+
113
+ app.register Sinatra::ActiveRecordExtension
114
+
115
+ # app.configure :development do
116
+ # ActiveRecord::Base.logger.level = Logger::DEBUG
117
+ # end
118
+ end
119
+
120
+ end
121
+
122
+ module Locales
123
+
124
+ def self.registered(app)
125
+ require 'i18n'
126
+ require 'i18n/backend/fallbacks'
127
+
128
+ cwd = Pathname.new(Dir.pwd)
129
+ app.set :locale, :es
130
+ app.set :locales_path, cwd.join('config', 'locales')
131
+
132
+ app.helpers do
133
+ def t(key); I18n.t(key); end
134
+ end
135
+
136
+ app.configure do
137
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
138
+ I18n.load_path = Dir[File.join(app.settings.locales_path, '*.yml')]
139
+ I18n.enforce_available_locales = false
140
+ I18n.backend.load_translations
141
+ end
142
+
143
+ app.before do
144
+ I18n.locale = app.settings.locale
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ # usage:
151
+
152
+ # class App < Snails::App
153
+ # register Snails::Assets
154
+ #
155
+ # # optional:
156
+ # set :assets_precompile, %w(js/app.js css/styles.css)
157
+ # # also optional, set compressor
158
+ # sprockets.css_compressor = :csso
159
+ # sprockets.js_compressor = :uglifier
160
+ # end
161
+
162
+ # Then, in your view:
163
+ #
164
+ # <script src="/assets/js/app.js"></script>
165
+ # <link rel="stylesheet" href="/assets/css/styles.css" />
166
+ #
167
+
168
+ module Assets
169
+
170
+ def self.registered(app)
171
+ require 'sprockets-helpers'
172
+
173
+ cwd = Pathname.new(Dir.pwd)
174
+ app.set :sprockets, Sprockets::Environment.new(cwd)
175
+ app.set :assets_prefix, '/assets' # URL
176
+ app.set :digest_assets, false
177
+ app.set :assets_public_path, -> { cwd.join('public', 'assets') } # output dir
178
+ app.set :assets_paths, %w(assets) # source files
179
+ app.set :assets_precompile, %w(js/main.js css/main.css)
180
+
181
+ app.configure do
182
+ app.assets_paths.each do |path|
183
+ app.sprockets.append_path cwd.join(path)
184
+ end
185
+ end
186
+
187
+ app.configure :production, :staging do
188
+ # app.sprockets.css_compressor = :sass
189
+ # app.sprockets.js_compressor = :uglifier
190
+ end
191
+
192
+ app.configure :development do
193
+ # allow asset requests to pass
194
+ # app.allow_paths.push /^#{app.assets_prefix}(\/\w+)?\/([\w\.-]+)/
195
+
196
+ # and serve them
197
+ app.get "#{app.assets_prefix}/*" do |path|
198
+ env_sprockets = request.env.dup
199
+ env_sprockets['PATH_INFO'] = path
200
+ app.sprockets.call(env_sprockets)
201
+ end
202
+ end
203
+
204
+ app.helpers do
205
+ def asset_path(filename)
206
+ file = manifest[filename] or raise "Not found in manifest: #{filename}"
207
+ [settings.assets_prefix, file].join('/')
208
+ end
209
+
210
+ if Snails.env.production?
211
+ def manifest
212
+ @manifest ||= read_manifest
213
+ end
214
+ else
215
+ def manifest
216
+ read_manifest
217
+ end
218
+ end
219
+
220
+ def read_manifest
221
+ file = Dir[settings.assets_public_path + '/.*.json'].first or raise "No manifest found at #{path}"
222
+ JSON.parse(IO.read(file))['assets']
223
+ end
224
+ end
225
+ end
226
+
227
+ module Tasks
228
+
229
+ def self.precompile_for(app)
230
+ unless app.respond_to?(:assets_public_path)
231
+ return puts "#{app.name} doesn't have the Asset module included."
232
+ end
233
+
234
+ puts "Precompiling #{app.name} assets to #{app.assets_public_path}..."
235
+ FileUtils.remove_dir(app.assets_public_path.to_s, true)
236
+
237
+ environment = app.sprockets
238
+ manifest = ::Sprockets::Manifest.new(environment.index, app.assets_public_path)
239
+ manifest.compile(app.assets_precompile)
240
+
241
+ # unless app.development?
242
+ # files = Dir[app.assets_public_path.to_s + '/*/*']
243
+ files = `find #{app.assets_public_path}`.split("\n").select { |f| f[/\.(js|css)/] }
244
+ remove_digests(files)
245
+ # end
246
+ end
247
+
248
+ private
249
+
250
+ def self.remove_digests(files)
251
+ puts "Removing digests from #{files.length} files..."
252
+ files.each do |file|
253
+ dir = File.dirname(file)
254
+ parts = File.basename(file).split(/-|\./)
255
+ if !parts[1] or parts[1].length < 10
256
+ # puts "This doesn't look like a digested file: #{file}. Skipping..."
257
+ next
258
+ end
259
+
260
+ dest = File.join(dir, "#{parts.first}.#{parts.last}").sub('.gz', '.' + parts.last(2).join('.'))
261
+ FileUtils.mv(file, dest)
262
+ puts " --> #{dest}"
263
+ end
264
+ end
265
+ end
266
+
267
+ end
268
+
269
+ module ViewHelpers
270
+
271
+ def action
272
+ request.path_info.gsub('/','').blank? ? 'home' : request.path_info.gsub('/',' ')
273
+ end
274
+
275
+ def partial(name, opts = {})
276
+ partial_name = name.to_s["/"] ? name.to_s.reverse.sub("/", "_/").reverse : "_#{name}"
277
+ erb(partial_name.to_sym, { layout: false }.merge(opts))
278
+ end
279
+
280
+ def view(view_name, opts = {})
281
+ layout = request.xhr? ? false : true
282
+ erb(view_name.to_sym, { layout: layout }.merge(opts))
283
+ end
284
+
285
+ #########################################
286
+ # formatting
287
+
288
+ def tag(name, options = nil, open = false)
289
+ attributes = tag_attributes(options)
290
+ "<#{name}#{attributes}#{open ? '>' : ' />'}"
291
+ end
292
+
293
+ def tag_attributes(options)
294
+ return '' unless options
295
+ options.inject('') do |all,(key,value)|
296
+ next all unless value
297
+ all << ' ' if all.empty?
298
+ all << %(#{key}="#{value}" )
299
+ end.chomp!(' ')
300
+ end
301
+
302
+ def simple_format(text, options = {})
303
+ t = options.delete(:tag) || :p
304
+ start_tag = tag(t, options, true)
305
+ text = text.to_s.dup
306
+ text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
307
+ text.gsub!(/\n\n+/, "</#{t}>\n\n#{start_tag}") # 2+ newline -> paragraph
308
+ text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
309
+ text.insert 0, start_tag
310
+ text << "</#{t}>"
311
+ text
312
+ end
313
+
314
+ #########################################
315
+ # forms
316
+
317
+ def form_input(object, field, options = {})
318
+ id, name, index, label = input_base(object, field, options)
319
+ type = (options[:type] || :text).to_sym
320
+ value = options[:value] ? "value='#{options[:value]}'" : (type == :password ? '' : "value='#{object.send(field)}'")
321
+
322
+ classes = object.errors[field].any? ? 'has-errors' : ''
323
+ label + raw_input(index, type, id, name, value, options[:placeholder], classes, options[:required])
324
+ end
325
+
326
+ def form_password(object, field, options = {})
327
+ form_input(object, field, {:type => 'password'}.merge(options))
328
+ end
329
+
330
+ def form_checkbox(object, field, options = {})
331
+ id, name, index, label = input_base(object, field, options)
332
+
333
+ type = options[:type] || :checkbox
334
+ value = options[:value] ? "value='#{options[:value]}'" : ''
335
+
336
+ if type.to_sym == :radio
337
+ checked = object.send(field) == options[:value]
338
+ value += checked ? " selected='selected'" : ''
339
+ else
340
+ checked = object.send(field)
341
+ value += checked ? " checked='true'" : ''
342
+ end
343
+
344
+ label + raw_input(index, type, id, name, options[:placeholder], value)
345
+ end
346
+
347
+ def form_textarea(object, field, options = {})
348
+ id, name, index, label = input_base(object, field, options)
349
+ style = options[:style] ? "style='#{options[:style]}'" : ''
350
+ label + "<textarea #{style} tabindex='#{index}' id='#{id}' name='#{name}'>#{object.send(field)}</textarea>"
351
+ end
352
+
353
+ def form_select_options(list, selected = nil)
354
+ list.map do |name, val|
355
+ val = name if val.nil?
356
+ sel = val == selected ? 'selected="selected"' : ''
357
+ "<option #{sel} value='#{val}'>#{name}</option>"
358
+ end.join("\n")
359
+ end
360
+
361
+ def form_put
362
+ '<input type="hidden" name="_method" value="put" />'
363
+ end
364
+
365
+ def form_submit(text = 'Actualizar', classes = '')
366
+ @tabindex = @tabindex ? @tabindex + 1 : 1
367
+ "<button tabindex='#{@tabindex}' class='primary #{classes}' type='submit'>#{text}</button>"
368
+ end
369
+
370
+ def post_button(text, path, opts = {})
371
+ form_id = opts.delete(:form_id) || path.gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
372
+ css_class = opts.delete(:css_class) || ''
373
+ input_html = opts.delete(:input_html) || ''
374
+ confirm_text = opts.delete(:confirm_text) || 'Seguro?'
375
+ submit_val = opts.delete(:value) || text
376
+ '<form id="' + form_id + '" style="display:inline" method="post" action="' + url(path) + '" onsubmit="return confirm(\'' + confirm_text + '\');">
377
+ ' + input_html + '
378
+ <button name="submit" type="submit" class="' + css_class + ' button" value="' + submit_val + '">' + text + '</button>
379
+ </form>'
380
+ end
381
+
382
+ def delete_link(options = {})
383
+ post_button(options[:text], options[:path], options.merge({
384
+ input_html: "<input type='hidden' name='_method' value='delete' />",
385
+ css_class: 'danger'
386
+ }))
387
+ end
388
+
389
+ protected
390
+
391
+ def input_base(object, field, options)
392
+ label = options[:label] || field.to_s.gsub('_', ' ').capitalize
393
+ example = options[:example] ? "<small class='example'>#{options[:example]}</small>" : ''
394
+
395
+ id = "#{get_model_name(object).downcase}_#{options[:key] || field}"
396
+ name = "#{get_model_name(object).downcase}[#{field}]"
397
+ label = options[:label] == false ? '' : "<label for='#{id}'>#{label}#{example}</label>\n"
398
+
399
+ @tabindex = @tabindex ? @tabindex + 1 : 1
400
+ return id, name, @tabindex, label
401
+ end
402
+
403
+ def raw_input(index, type, id, name, value, placeholder = '', classes = '', required = false)
404
+ req = required ? "required" : ''
405
+ "<input #{req} class='#{classes}' tabindex='#{index}' type='#{type}' id='#{id}' name='#{name}' placeholder='#{placeholder}' #{value} />"
406
+ end
407
+
408
+ def get_model_name(obj)
409
+ obj.respond_to?(:field_name) ? obj.field_name : get_class_name(obj.class)
410
+ end
411
+
412
+ def get_class_name(klass)
413
+ klass.model_name.to_s.split("::").last
414
+ end
415
+
416
+ end
417
+
418
+ end
data/snails.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # require File.expand_path("../lib/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "snails"
6
+ s.version = '0.0.1'
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Tomás Pollak']
9
+ s.email = ['tomas@forkhq.com']
10
+ # s.homepage = "https://github.com/tomas/snails"
11
+ s.summary = "A simple-and-nimble version of Rails."
12
+ s.description = "A simple-and-nimble version of Rails."
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.add_development_dependency "bundler", ">= 1.0.0"
16
+ s.add_development_dependency "rspec", '~> 3.0', '>= 3.0.0'
17
+
18
+ s.add_runtime_dependency "i18n", ">= 1.0.1"
19
+ s.add_runtime_dependency "sinatra-contrib", ">= 2.0.3"
20
+ s.add_runtime_dependency "sinatra-activerecord", ">= 2.0.13"
21
+ s.add_runtime_dependency "sinatra-flash", ">= 0.3.0"
22
+ s.add_runtime_dependency "sprockets-helpers", ">= 1.2"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
26
+ s.require_path = 'lib'
27
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tomás Pollak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 3.0.0
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: i18n
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.0.1
61
+ - !ruby/object:Gem::Dependency
62
+ name: sinatra-contrib
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.0.3
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.0.3
75
+ - !ruby/object:Gem::Dependency
76
+ name: sinatra-activerecord
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.0.13
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 2.0.13
89
+ - !ruby/object:Gem::Dependency
90
+ name: sinatra-flash
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 0.3.0
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.3.0
103
+ - !ruby/object:Gem::Dependency
104
+ name: sprockets-helpers
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '1.2'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '1.2'
117
+ description: A simple-and-nimble version of Rails.
118
+ email:
119
+ - tomas@forkhq.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - ".gitignore"
125
+ - Rakefile
126
+ - example/Gemfile
127
+ - example/Rakefile
128
+ - example/config.ru
129
+ - lib/snails.rb
130
+ - lib/snails/tasks.rb
131
+ - snails.gemspec
132
+ homepage:
133
+ licenses: []
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 1.3.6
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.6.13
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: A simple-and-nimble version of Rails.
155
+ test_files: []