racket-mvc 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63921b5e05aa5980041e8817c71bcd43b015834d
4
- data.tar.gz: 1f98ea84f965aa6c36707dd515c16b9d7ff7ae29
3
+ metadata.gz: 63dc217836153cda7cd00f43dfe894a519c4de5d
4
+ data.tar.gz: 22bca87d176624f95ff10c8007526c0d2b2f4ca0
5
5
  SHA512:
6
- metadata.gz: 306ae0910122403e2adff6a1cab12d01bea39e17d7142e7b07a2ef0aa1a48e1b61c38e1debf2f2ffff3f38bebc1d9d12cd3f2770dd0509ac44e0bdb2e95c92c2
7
- data.tar.gz: 3fdeac0f87e45d5cefda65f18ebc61d831028a09ce75ffe371f2024838e6b5f06087fe655f64fb8d405fc1e0515167d27b42f74c5c8bcf472c0371ebf05bd11a
6
+ metadata.gz: 332598c7ab0f25fceb9a21356d13d96700f3bb1ba592a71a46b048f365ff7066078230348e7a6432430015f7c6293531b75c3ed560c4a1ad02f0c315363a5046
7
+ data.tar.gz: 33a1208769de0e009036df394fea74a5fa6630b035f35bb7f952f0887873514050a6739dcd126446b2bf1ac2cab1f0eff64d83abd62c38a52c019593fc9e613b
@@ -33,6 +33,12 @@ module Racket
33
33
  @application ||= Utils.build_application(self)
34
34
  end
35
35
 
36
+ # Applies settings.
37
+ def self.apply_settings(settings)
38
+ fail 'Application has already been initialized!' if @settings
39
+ @settings = Settings::Application.new(settings)
40
+ end
41
+
36
42
  def self.calculate_url_path(file)
37
43
  url_path = "/#{file.relative_path_from(@settings.controller_dir).dirname}"
38
44
  url_path = '' if url_path == '/.'
@@ -104,8 +110,8 @@ module Racket
104
110
  # @param [Hash] settings
105
111
  # @return [Class]
106
112
  def self.init(settings = {})
107
- fail 'Application has already been initialized!' if @settings
108
- @settings = Settings::Application.new(settings)
113
+ apply_settings(settings)
114
+ application # This will make sure all plugins and helpers are loaded before any controllers
109
115
  setup_static_server
110
116
  reload
111
117
  self
@@ -129,8 +135,9 @@ module Racket
129
135
  def self.load_controller_file(file)
130
136
  ::Kernel.require file
131
137
  klass = @settings.fetch(:last_added_controller).pop
132
- Utils.apply_helpers(klass)
133
- @router.map(calculate_url_path(file), klass) && nil
138
+ # Helpers may do stuff based on route, make sure it is available before applying helpers.
139
+ @router.map(calculate_url_path(file), klass)
140
+ Utils.apply_helpers(klass) && nil
134
141
  end
135
142
 
136
143
  def self.load_controller_files
@@ -156,13 +163,6 @@ module Racket
156
163
  (::Kernel.require Utils.build_path(*args)) && nil
157
164
  end
158
165
 
159
- # Resets Racket::Application, making it possible to run run a new application with new settings.
160
- # This is a workaround for Racket::Application being a singleton, making tests harder to write,
161
- # @todo Remove this when Racket::Application stops beeing a singleton (if ever).
162
- def self.reset!
163
- instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
164
- end
165
-
166
166
  # Serves a static file (if Racket is configured to serve static files).
167
167
  #
168
168
  # @param [Hash] env Rack environment
@@ -0,0 +1,66 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Racket
20
+ # Helpers module
21
+ module Helpers
22
+ # Helper module that allows CSS files to be loaded dynamically using SASS.
23
+ module Sass
24
+ # Get route to CSS, which will use SASS in the background to deliver the CSS.
25
+ #
26
+ # @param [Symbol] sym
27
+ # @return [String]
28
+ def css(sym)
29
+ route = Application.get_route(self.class)
30
+ route = '' if route == '/' # Special case for root controller
31
+ "/css#{route}/#{sym}.css"
32
+ end
33
+
34
+ def self.add_template_location(route)
35
+ root_dir = Application.settings.root_dir
36
+ sass_dir = Utils.build_path(root_dir, 'sass', route).to_s
37
+ css_dir = Utils.build_path(root_dir, 'public', 'css', route).to_s
38
+ ::Sass::Plugin.add_template_location(sass_dir, css_dir)
39
+ sass_dir
40
+ end
41
+
42
+ def self.add_warmup_urls(sass_dir, route)
43
+ Dir.chdir(sass_dir) do
44
+ basedir = route.empty? ? '/css' : "/css/#{route}"
45
+ Dir.glob('*.s[ac]ss').each do |file|
46
+ Application.settings.warmup_urls << "#{basedir}/#{::File.basename(file, '.*')}.css"
47
+ end
48
+ end
49
+ end
50
+
51
+ # Whenever this helper is included in a controller it will setup a link between
52
+ # a SASS directory and a CSS directory.
53
+ #
54
+ # @param [Class] klass
55
+ # @return [nil]
56
+ def self.included(klass)
57
+ route = Application.get_route(klass)[1..-1] # Remove leading slash
58
+ sass_dir = add_template_location(route)
59
+ add_warmup_urls(sass_dir, route)
60
+ nil
61
+ end
62
+
63
+ private_class_method :add_template_location, :add_warmup_urls
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,54 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Racket
20
+ # Namespace for plugins.
21
+ module Plugins
22
+ # Plugin base class. All plugins should inherit from this class.
23
+ class Base
24
+ attr_reader :settings
25
+
26
+ def initialize(settings = {})
27
+ @settings = {}
28
+ @settings.merge!(settings) if settings.is_a?(Hash)
29
+ end
30
+
31
+ # This method should return an array of helpers (symbols) that the plugin wants to load
32
+ # automatically in every controller. If you do not want your controller to load any helpers
33
+ # automatically you do not need to override this method. You can still add your helpers to
34
+ # individual controllers by using Controller#helper.
35
+ #
36
+ # @return [Array] An array of symbols representing helpers that should be loaded automatically
37
+ def default_controller_helpers
38
+ []
39
+ end
40
+
41
+ # This method should return an array of [Module, Hash] arrays where each module represenents
42
+ # a Rack-compatible middleware module and the hash the settings that should be applied to
43
+ # that middleware. Each pair that the plugin provides will be automatically added to the
44
+ # middleware of the application. This is just for conveniance, a user could still add The same
45
+ # middleware using the global settings.
46
+ #
47
+ # @return [Array] An array of [Module, Hash] pairs that represenents middleware that will be
48
+ # loaded automatically by the plugin
49
+ def middleware
50
+ []
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require_relative 'base'
20
+
21
+ module Racket
22
+ # Namespace for plugins.
23
+ module Plugins
24
+ # Sass plugin.
25
+ class Sass < Base
26
+ # Called on plugin initialization.
27
+ def initialize(settings = nil)
28
+ super
29
+ begin
30
+ require 'sass/plugin/rack'
31
+ rescue LoadError
32
+ raise 'Failed to load sass rack plugin!'
33
+ end
34
+ apply_sass_settings
35
+ end
36
+
37
+ # Middleware that should be automatically added.
38
+ def middleware
39
+ [[::Sass::Plugin::Rack, nil]]
40
+ end
41
+
42
+ private
43
+
44
+ # Apply each setting to the Sass plugin.
45
+ def apply_sass_settings
46
+ settings.each_pair { |key, value| ::Sass::Plugin.options[key] = value }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -32,6 +32,7 @@ module Racket
32
32
  setting(:logger, Logger.new($stdout))
33
33
  setting(:middleware, [])
34
34
  setting(:mode, :live)
35
+ setting(:plugins, [])
35
36
  setting(
36
37
  :session_handler,
37
38
  [
@@ -44,6 +45,7 @@ module Racket
44
45
  ]
45
46
  )
46
47
  setting(:root_dir, nil) # Will be set automatically by constructor.
48
+ setting(:warmup_urls, Set.new)
47
49
 
48
50
  def initialize(defaults = {})
49
51
  defaults[:root_dir] = Dir.pwd unless defaults.key?(:root_dir)
@@ -34,7 +34,8 @@ module Racket
34
34
  #
35
35
  # @return [Proc]
36
36
  def build
37
- expand_middleware_list
37
+ init_plugins
38
+ add_warmup_hook
38
39
  add_middleware
39
40
  @builder.run(application_proc)
40
41
  @builder
@@ -44,6 +45,7 @@ module Racket
44
45
 
45
46
  # Add middleware to the builder.
46
47
  def add_middleware
48
+ expand_middleware_list
47
49
  @middleware.each do |ware|
48
50
  klass, opts = ware
49
51
  @application.inform_dev("Loading middleware #{klass} with settings #{opts.inspect}.")
@@ -51,6 +53,16 @@ module Racket
51
53
  end
52
54
  end
53
55
 
56
+ # Add a list of urls to visit on startup
57
+ def add_warmup_hook
58
+ warmup_urls = Racket::Application.settings.warmup_urls
59
+ return if warmup_urls.empty?
60
+ @builder.warmup do |app|
61
+ client = Rack::MockRequest.new(app)
62
+ visit_warmup_urls(client, warmup_urls)
63
+ end
64
+ end
65
+
54
66
  # Returns a lambda that represenents that application flow.
55
67
  def application_proc
56
68
  application = @application
@@ -69,6 +81,42 @@ module Racket
69
81
  @middleware.unshift([Rack::ContentType, default_content_type]) if default_content_type
70
82
  @middleware.unshift([Rack::ShowExceptions]) if @application.dev_mode?
71
83
  end
84
+
85
+ # Initializes plugins.
86
+ def init_plugins
87
+ @settings.plugins.each do |plugin_info|
88
+ plugin_instance = self.class.get_plugin_instance(*plugin_info)
89
+ run_plugin_hooks(plugin_instance)
90
+ # TODO: Store plugin instance somewhere in application settings
91
+ end
92
+ end
93
+
94
+ # Runs plugin hooks.
95
+ def run_plugin_hooks(plugin_obj)
96
+ @middleware.concat(plugin_obj.middleware)
97
+ @settings.default_controller_helpers.concat(plugin_obj.default_controller_helpers)
98
+ end
99
+
100
+ # Visits a list of warmup URLs.
101
+ def visit_warmup_urls(client, urls)
102
+ urls.each do |url|
103
+ @application.inform_dev("Visiting warmup url #{url}.")
104
+ client.get(url)
105
+ end
106
+ end
107
+
108
+ # Returns an instance of a specific plugin.
109
+ #
110
+ # @param [Symbol] plugin
111
+ # @param [Hash|nil] settings
112
+ # @return [Object] An instance of the requested plugin class
113
+ def self.get_plugin_instance(plugin, settings)
114
+ Utils.safe_require("racket/plugins/#{plugin}.rb")
115
+ # TODO: Allow custom plugins dir as well
116
+ klass =
117
+ Racket::Plugins.const_get(plugin.to_s.split('_').collect(&:capitalize).join.to_sym)
118
+ klass.new(settings)
119
+ end
72
120
  end
73
121
 
74
122
  # Builds and returns a Rack::Builder using the provided Racket::Application
@@ -153,10 +153,16 @@ module Racket
153
153
  # @return [String|Proc|nil]
154
154
  def self.lookup_template_with_default(path, default_template)
155
155
  template = lookup_template(path)
156
- if !template && (default_template.is_a?(String) || default_template.is_a?(Symbol))
157
- template = lookup_template(Utils.fs_path(path.dirname, default_template))
156
+ unless template
157
+ if default_template.is_a?(String) || default_template.is_a?(Symbol)
158
+ # Strings and symbols can be lookup up in the file system...
159
+ template = lookup_template(Utils.fs_path(path.dirname, default_template))
160
+ else
161
+ # ...but not nils/procs!
162
+ template = default_template
163
+ end
158
164
  end
159
- template || default_template
165
+ template
160
166
  end
161
167
  end
162
168
 
@@ -23,9 +23,9 @@ module Racket
23
23
  # Major version
24
24
  MAJOR = 0
25
25
  # Minor version
26
- MINOR = 3
26
+ MINOR = 4
27
27
  # Teeny version
28
- TEENY = 3
28
+ TEENY = 0
29
29
  # Is it a prerelease?
30
30
  PRERELEASE = false
31
31
 
@@ -1,4 +1,4 @@
1
- describe 'A custom Racket test Application' do
1
+ describe 'A custom Racket test application' do
2
2
  extend Rack::Test::Methods
3
3
  def app
4
4
  @app ||= Racket::Application.using(
@@ -15,6 +15,20 @@ describe 'A custom Racket test Application' do
15
15
  app.settings.view_dir.should.equal(Racket::Utils.build_path('templates'))
16
16
  end
17
17
 
18
+ it 'should get the correct middleware' do
19
+ middleware = app.settings.middleware
20
+ middleware.length.should.equal(3)
21
+ middleware[0].class.should.equal(Array)
22
+ middleware[0].length.should.equal(1)
23
+ middleware[0].first.should.equal(Rack::ShowExceptions)
24
+ middleware[1].class.should.equal(Array)
25
+ middleware[1].length.should.equal(2)
26
+ middleware[1].first.should.equal(Rack::ContentType)
27
+ middleware[2].class.should.equal(Array)
28
+ middleware[2].length.should.equal(2)
29
+ middleware[2].first.should.equal(Rack::Session::Cookie)
30
+ end
31
+
18
32
  it 'should be able to get/set settings on controller' do
19
33
  get '/sub3/a_secret_place'
20
34
  last_response.status.should.equal(302)
@@ -1,4 +1,4 @@
1
- describe 'A default Racket test Application' do
1
+ describe 'A default Racket test application' do
2
2
  extend Rack::Test::Methods
3
3
 
4
4
  def app
@@ -47,6 +47,18 @@ describe 'A default Racket test Application' do
47
47
  JSON.parse(last_response.body).should.equal(%w(foo bar baz))
48
48
  end
49
49
 
50
+ it 'should get the correct middleware' do
51
+ middleware = app.settings.middleware
52
+ middleware.length.should.equal(2)
53
+ middleware[0].class.should.equal(Array)
54
+ middleware[0].length.should.equal(2)
55
+ middleware[0].first.should.equal(Rack::ContentType)
56
+ middleware[1].class.should.equal(Array)
57
+ middleware[1].length.should.equal(2)
58
+ middleware[1].first.should.equal(Rack::Session::Cookie)
59
+ true.should.equal(true)
60
+ end
61
+
50
62
  it 'returns the correct respnse when calling index action' do
51
63
  # RootController
52
64
  get '/'
@@ -0,0 +1,44 @@
1
+ describe 'A Racket application with plugins' do
2
+ extend Rack::Test::Methods
3
+ def app
4
+ @app ||= Racket::Application.using(
5
+ logger: nil,
6
+ plugins: [[:sass, { style: :compressed, sourcemap: :none }]]
7
+ )
8
+ end
9
+
10
+ it 'should get correct defaults from base plugin' do
11
+ require 'racket/plugins/base'
12
+ plugin = Racket::Plugins::Base.new
13
+ plugin.default_controller_helpers.should.equal([])
14
+ plugin.middleware.should.equal([])
15
+ end
16
+
17
+ it 'should return correct CSS paths when querying default controller' do
18
+ get '/css_path'
19
+ last_response.status.should.equal(200)
20
+ last_response.body.should.equal('/css/foo.css')
21
+ end
22
+
23
+ it 'should return correct CSS paths when querying a subcontroller' do
24
+ get '/sub/css_path'
25
+ last_response.status.should.equal(200)
26
+ last_response.body.should.equal('/css/sub/bar.css')
27
+ end
28
+
29
+ =begin
30
+ it 'should render a SASS based stylesheet for the default controller' do
31
+ get '/css/test.css'
32
+ last_response.status.should.equal(200)
33
+ last_response.headers['Content-Type'].should.equal('text/css')
34
+ last_response.body.should.equal("body{background-color:snow;color:black}\n")
35
+ end
36
+
37
+ it 'should render a SASS based stylesheet for a subcontroller' do
38
+ get '/css/sub/test.css'
39
+ last_response.status.should.equal(200)
40
+ last_response.headers['Content-Type'].should.equal('text/css')
41
+ last_response.body.should.equal("body{background-color:steelblue;color:gray}\n")
42
+ end
43
+ =end
44
+ end
@@ -14,6 +14,7 @@ end
14
14
  TEST_DIR = File.absolute_path(File.dirname(__FILE__))
15
15
  TEST_DEFAULT_APP_DIR = File.join(TEST_DIR, 'test_default_app')
16
16
  TEST_CUSTOM_APP_DIR = File.join(TEST_DIR, 'test_custom_app')
17
+ TEST_PLUGIN_APP_DIR = File.join(TEST_DIR, 'test_plugin_app')
17
18
 
18
19
  require 'racket'
19
20
 
@@ -29,7 +30,8 @@ require File.join(TEST_DIR, '_request.rb')
29
30
  # Application tests.
30
31
  suites = [
31
32
  -> { Dir.chdir(TEST_DEFAULT_APP_DIR) { require File.join(TEST_DIR, '_default.rb') } },
32
- -> { Dir.chdir(TEST_CUSTOM_APP_DIR) { require File.join(TEST_DIR, '_custom.rb') } }
33
+ -> { Dir.chdir(TEST_CUSTOM_APP_DIR) { require File.join(TEST_DIR, '_custom.rb') } },
34
+ -> { Dir.chdir(TEST_PLUGIN_APP_DIR) { require File.join(TEST_DIR, '_plugin.rb') } }
33
35
  ]
34
36
 
35
37
  # Leave off randomization for now. Sessions does not seem to be reset correctly between suites!
@@ -37,7 +39,14 @@ suites = [
37
39
 
38
40
  # Run application tests.
39
41
  suites.each do |suite|
40
- Racket::Application.reset!
42
+ # Make sure Racket::Application and Racket::Settings::Application is reset between the suites.
43
+ # Racket::Application@application and Racket::Settings::Application must be pristine on each run!
44
+ Racket.class_eval { remove_const(:Application) }
45
+ Racket::Settings.class_eval { remove_const(:Application) }
46
+ load('lib/racket/application.rb')
47
+ load('lib/racket/settings/application.rb')
48
+
49
+ # Run suite
41
50
  suite.call
42
51
  end
43
52
 
@@ -0,0 +1,8 @@
1
+ # Main controller
2
+ class MainController < Racket::Controller
3
+ helper :sass
4
+
5
+ def css_path
6
+ css(:foo)
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # Sub controller
2
+ class SubController < Racket::Controller
3
+ helper :sass
4
+
5
+ def css_path
6
+ css(:bar)
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ body {
2
+ background-color: steelblue;
3
+ color: gray;
4
+ }
@@ -0,0 +1,4 @@
1
+ body {
2
+ background-color: snow;
3
+ color: black;
4
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racket-mvc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Olsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-29 00:00:00.000000000 Z
11
+ date: 2016-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http_router
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '10'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sass
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.4'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.4'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: simplecov
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -151,7 +165,10 @@ files:
151
165
  - lib/racket/current.rb
152
166
  - lib/racket/helpers/file.rb
153
167
  - lib/racket/helpers/routing.rb
168
+ - lib/racket/helpers/sass.rb
154
169
  - lib/racket/helpers/view.rb
170
+ - lib/racket/plugins/base.rb
171
+ - lib/racket/plugins/sass.rb
155
172
  - lib/racket/request.rb
156
173
  - lib/racket/response.rb
157
174
  - lib/racket/router.rb
@@ -172,6 +189,7 @@ files:
172
189
  - spec/_custom.rb
173
190
  - spec/_default.rb
174
191
  - spec/_invalid.rb
192
+ - spec/_plugin.rb
175
193
  - spec/_request.rb
176
194
  - spec/racket.rb
177
195
  - spec/test_custom_app/controllers/sub1/custom_sub_controller_1.rb
@@ -195,6 +213,10 @@ files:
195
213
  - spec/test_default_app/controllers/sub3/default_sub_controller_3.rb
196
214
  - spec/test_default_app/controllers/sub3/inherited/default_inherited_controller.rb
197
215
  - spec/test_default_app/public/hello.txt
216
+ - spec/test_plugin_app/controllers/main.rb
217
+ - spec/test_plugin_app/controllers/sub/sub.rb
218
+ - spec/test_plugin_app/sass/sub/test.scss
219
+ - spec/test_plugin_app/sass/test.scss
198
220
  homepage: https://github.com/lasso/racket
199
221
  licenses:
200
222
  - GNU AFFERO GENERAL PUBLIC LICENSE, version 3