lennarb 1.3.0 → 1.4.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.
data/lib/lennarb.rb CHANGED
@@ -1,158 +1,20 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023-2024, by Aristóteles Coutinho.
5
-
6
1
  # Core extensions
7
2
  #
8
- require 'pathname'
9
- require 'rack'
10
-
11
- require_relative 'lennarb/plugin'
12
- require_relative 'lennarb/request'
13
- require_relative 'lennarb/response'
14
- require_relative 'lennarb/route_node'
15
- require_relative 'lennarb/version'
16
-
17
- class Lennarb
18
- class LennarbError < StandardError; end
19
-
20
- attr_reader :_root, :_plugins, :_loaded_plugins, :_middlewares, :_app
21
-
22
- def self.use(middleware, *args, &block)
23
- @_middlewares ||= []
24
- @_middlewares << [middleware, args, block]
25
- end
26
-
27
- def self.get(path, &block) = add_route(path, :GET, block)
28
- def self.put(path, &block) = add_route(path, :PUT, block)
29
- def self.post(path, &block) = add_route(path, :POST, block)
30
- def self.head(path, &block) = add_route(path, :HEAD, block)
31
- def self.patch(path, &block) = add_route(path, :PATCH, block)
32
- def self.delete(path, &block) = add_route(path, :DELETE, block)
33
- def self.options(path, &block) = add_route(path, :OPTIONS, block)
34
-
35
- def self.inherited(subclass)
36
- super
37
- subclass.instance_variable_set(:@_root, RouteNode.new)
38
- subclass.instance_variable_set(:@_plugins, [])
39
- subclass.instance_variable_set(:@_middlewares, @_middlewares&.dup || [])
40
-
41
- Plugin.load_defaults! if Plugin.load_defaults?
42
- end
43
-
44
- def self.plugin(plugin_name, *, &)
45
- @_loaded_plugins ||= {}
46
- @_plugins ||= []
47
-
48
- return if @_loaded_plugins.key?(plugin_name)
49
-
50
- plugin_module = Plugin.load(plugin_name)
51
- plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)
52
-
53
- @_loaded_plugins[plugin_name] = plugin_module
54
- @_plugins << plugin_name
55
- end
56
-
57
- def self.freeze!
58
- app = new
59
- app.freeze!
60
- app
61
- end
62
-
63
- def self.add_route(path, http_method, block)
64
- @_root ||= RouteNode.new
65
- parts = path.split('/').reject(&:empty?)
66
- @_root.add_route(parts, http_method, block)
67
- end
68
-
69
- private_class_method :add_route
70
-
71
- def initialize
72
- @_mutex = Mutex.new
73
- @_root = self.class.instance_variable_get(:@_root)&.dup || RouteNode.new
74
- @_plugins = self.class.instance_variable_get(:@_plugins)&.dup || []
75
- @_loaded_plugins = self.class.instance_variable_get(:@_loaded_plugins)&.dup || {}
76
- @_middlewares = self.class.instance_variable_get(:@_middlewares)&.dup || []
77
-
78
- build_app
79
-
80
- yield self if block_given?
81
- end
82
-
83
- def call(env) = @_mutex.synchronize { @_app.call(env) }
84
-
85
- def freeze!
86
- return self if @_mounted
87
-
88
- @_root.freeze
89
- @_plugins.freeze
90
- @_loaded_plugins.freeze
91
- @_middlewares.freeze
92
- @_app.freeze if @_app.respond_to?(:freeze)
93
- self
94
- end
95
-
96
- def get(path, &block) = add_route(path, :GET, block)
97
- def put(path, &block) = add_route(path, :PUT, block)
98
- def post(path, &block) = add_route(path, :POST, block)
99
- def head(path, &block) = add_route(path, :HEAD, block)
100
- def patch(path, &block) = add_route(path, :PATCH, block)
101
- def delete(path, &block) = add_route(path, :DELETE, block)
102
- def options(path, &block) = add_route(path, :OPTIONS, block)
103
-
104
- def plugin(plugin_name, *, &)
105
- return if @_loaded_plugins.key?(plugin_name)
106
-
107
- plugin_module = Plugin.load(plugin_name)
108
- self.class.extend plugin_module::ClassMethods if plugin_module.const_defined?(:ClassMethods)
109
- self.class.include plugin_module::InstanceMethods if plugin_module.const_defined?(:InstanceMethods)
110
- plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)
111
- @_loaded_plugins[plugin_name] = plugin_module
112
- @_plugins << plugin_name
113
- end
114
-
115
- private
116
-
117
- def build_app
118
- @_app = method(:process_request)
119
-
120
- @_middlewares.reverse_each do |middleware, args, block|
121
- @_app = middleware.new(@_app, *args, &block)
122
- end
123
- end
124
-
125
- def process_request(env)
126
- http_method = env[Rack::REQUEST_METHOD].to_sym
127
- parts = env[Rack::PATH_INFO].split('/').reject(&:empty?)
128
-
129
- block, params = @_root.match_route(parts, http_method)
130
- return not_found unless block
131
-
132
- res = Response.new
133
- req = Request.new(env, params)
134
-
135
- catch(:halt) do
136
- instance_exec(req, res, &block)
137
- res.finish
138
- end
139
- rescue StandardError => e
140
- handle_error(e)
141
- end
142
-
143
- def handle_error(error)
144
- case error
145
- when ArgumentError
146
- [400, { 'content-type' => 'text/plain' }, ["Bad Request: #{error.message}"]]
147
- else
148
- [500, { 'content-type' => 'text/plain' }, ["Internal Server Error: #{error.message}"]]
149
- end
150
- end
151
-
152
- def not_found = [404, { 'content-type' => 'text/plain' }, ['Not Found']]
153
-
154
- def add_route(path, http_method, block)
155
- parts = path.split('/').reject(&:empty?)
156
- @_root.add_route(parts, http_method, block)
157
- end
3
+ require "bundler"
4
+ require "rack"
5
+ require "json"
6
+ require "pathname"
7
+ require "superconfig"
8
+
9
+ module Lennarb
10
+ require_relative "lennarb/constansts"
11
+ require_relative "lennarb/environment"
12
+ require_relative "lennarb/version"
13
+ require_relative "lennarb/request_handler"
14
+ require_relative "lennarb/request"
15
+ require_relative "lennarb/response"
16
+ require_relative "lennarb/route_node"
17
+ require_relative "lennarb/routes"
18
+ require_relative "lennarb/config"
19
+ require_relative "lennarb/app"
158
20
  end
data/logo/lennarb.png ADDED
Binary file
data/readme.md CHANGED
@@ -1,16 +1,35 @@
1
- # Lennarb
1
+ <div align="center">
2
+ <picture>
3
+ <img alt="Lennarb" src="logo/lennarb.png" width="250">
4
+ </picture>
2
5
 
3
- Lennarb is a lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.0+
6
+ ---
4
7
 
5
- **Basic Usage**
8
+ A lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.4+
9
+
10
+ [![Test](https://github.com/aristotelesbr/lennarb/actions/workflows/test.yaml/badge.svg)](https://github.com/aristotelesbr/lennarb/actions/workflows/test.yaml)
11
+ [![Gem](https://img.shields.io/gem/v/lennarb.svg)](https://rubygems.org/gems/lennarb)
12
+ [![Gem](https://img.shields.io/gem/dt/lennarb.svg)](https://rubygems.org/gems/lennarb)
13
+ [![MIT License](https://img.shields.io/:License-MIT-blue.svg)](https://tldrlegal.com/license/mit-license)
14
+
15
+ </div>
16
+
17
+ ## Basic Usage
6
18
 
7
19
  ```ruby
8
20
  require "lennarb"
9
21
 
10
- Lennarb.new do |router|
11
- router.get("/hello/:name") do |req, res|
12
- name = req.params[:name]
13
- res.html("Hello, #{name}!")
22
+ Lennarb::App.new do
23
+ config do
24
+ optional :env, string, "prodcution"
25
+ optional :port, int, 9292
26
+ end
27
+
28
+ routes do
29
+ get("/hello/:name") do |req, res|
30
+ name = req.params[:name]
31
+ res.html("Hello, #{name}!")
32
+ end
14
33
  end
15
34
  end
16
35
  ```
@@ -32,18 +51,18 @@ See all [graphs](https://github.com/aristotelesbr/lennarb/blob/main/benchmark)
32
51
 
33
52
  This table ranks the routers by the number of requests they can process per second. Higher numbers indicate better performance.
34
53
 
35
- Plese see [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) for more information.
54
+ Please see [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) for more information.
36
55
 
37
56
  ## Usage
38
57
 
39
- - [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
58
+ - [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
40
59
 
41
- - [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) - The **Lennarb** is very fast. The following benchmarks were performed on a MacBook Pro (Retina, 13-inch, Early 2013) with 2,7 GHz Intel Core i7 and 8 GB 1867 MHz DDR3. Based on [jeremyevans/r10k](https://github.com/jeremyevans/r10k) using the following [template build](static/r10k/build/lennarb.rb).
60
+ - [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) - The **Lennarb** is very fast. The following benchmarks were performed on a MacBook Pro (Retina, 13-inch, Early 2013) with 2,7 GHz Intel Core i7 and 8 GB 1867 MHz DDR3. Based on [jeremyevans/r10k](https://github.com/jeremyevans/r10k) using the following [template build](static/r10k/build/lennarb.rb).
42
61
 
43
- - [Plugin](https://aristotelesbr.github.io/lennarb/guides/plugin/index.html) - You can create your plugins to extend the functionality of the framework.
62
+ - [Request]() - TODO
44
63
 
45
- - [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
46
- The `res` object is used to send a response to the client. The Lennarb use a custom response object to send responses to the client. The `res` object is an instance of `Lennarb::Response`.
64
+ - [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
65
+ The `res` object is used to send a response to the client. The Lennarb use a custom response object to send responses to the client. The `res` object is an instance of `Lennarb::Response`.
47
66
 
48
67
  ### Developer Certificate of Origin
49
68
 
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lennarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aristóteles Coutinho
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-11-21 00:00:00.000000000 Z
10
+ date: 2025-02-26 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bigdecimal
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: colorize
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -30,164 +43,240 @@ dependencies:
30
43
  requirements:
31
44
  - - "~>"
32
45
  - !ruby/object:Gem::Version
33
- version: '3.0'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 3.0.8
46
+ version: '3.1'
37
47
  type: :runtime
38
48
  prerelease: false
39
49
  version_requirements: !ruby/object:Gem::Requirement
40
50
  requirements:
41
51
  - - "~>"
42
52
  - !ruby/object:Gem::Version
43
- version: '3.0'
53
+ version: '3.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: superconfig
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
44
65
  - - ">="
45
66
  - !ruby/object:Gem::Version
46
- version: 3.0.8
67
+ version: '0'
47
68
  - !ruby/object:Gem::Dependency
48
- name: bake
69
+ name: bundler
49
70
  requirement: !ruby/object:Gem::Requirement
50
71
  requirements:
51
- - - "~>"
72
+ - - ">="
52
73
  - !ruby/object:Gem::Version
53
- version: 0.18.2
74
+ version: '0'
54
75
  type: :development
55
76
  prerelease: false
56
77
  version_requirements: !ruby/object:Gem::Requirement
57
78
  requirements:
58
- - - "~>"
79
+ - - ">="
59
80
  - !ruby/object:Gem::Version
60
- version: 0.18.2
81
+ version: '0'
61
82
  - !ruby/object:Gem::Dependency
62
- name: bundler
83
+ name: covered
63
84
  requirement: !ruby/object:Gem::Requirement
64
85
  requirements:
65
- - - "~>"
86
+ - - ">="
66
87
  - !ruby/object:Gem::Version
67
- version: '2.2'
88
+ version: '0'
68
89
  type: :development
69
90
  prerelease: false
70
91
  version_requirements: !ruby/object:Gem::Requirement
71
92
  requirements:
72
- - - "~>"
93
+ - - ">="
73
94
  - !ruby/object:Gem::Version
74
- version: '2.2'
95
+ version: '0'
75
96
  - !ruby/object:Gem::Dependency
76
- name: covered
97
+ name: simplecov
77
98
  requirement: !ruby/object:Gem::Requirement
78
99
  requirements:
79
- - - "~>"
100
+ - - ">="
80
101
  - !ruby/object:Gem::Version
81
- version: 0.25.1
102
+ version: '0'
82
103
  type: :development
83
104
  prerelease: false
84
105
  version_requirements: !ruby/object:Gem::Requirement
85
106
  requirements:
86
- - - "~>"
107
+ - - ">="
87
108
  - !ruby/object:Gem::Version
88
- version: 0.25.1
109
+ version: '0'
89
110
  - !ruby/object:Gem::Dependency
90
111
  name: minitest
91
112
  requirement: !ruby/object:Gem::Requirement
92
113
  requirements:
93
- - - "~>"
114
+ - - ">="
94
115
  - !ruby/object:Gem::Version
95
- version: '5.20'
116
+ version: '0'
96
117
  type: :development
97
118
  prerelease: false
98
119
  version_requirements: !ruby/object:Gem::Requirement
99
120
  requirements:
100
- - - "~>"
121
+ - - ">="
101
122
  - !ruby/object:Gem::Version
102
- version: '5.20'
123
+ version: '0'
103
124
  - !ruby/object:Gem::Dependency
104
- name: puma
125
+ name: minitest-utils
105
126
  requirement: !ruby/object:Gem::Requirement
106
127
  requirements:
107
- - - "~>"
128
+ - - ">="
108
129
  - !ruby/object:Gem::Version
109
- version: '6.4'
130
+ version: '0'
110
131
  type: :development
111
132
  prerelease: false
112
133
  version_requirements: !ruby/object:Gem::Requirement
113
134
  requirements:
114
- - - "~>"
135
+ - - ">="
115
136
  - !ruby/object:Gem::Version
116
- version: '6.4'
137
+ version: '0'
117
138
  - !ruby/object:Gem::Dependency
118
139
  name: rack-test
119
140
  requirement: !ruby/object:Gem::Requirement
120
141
  requirements:
121
- - - "~>"
142
+ - - ">="
122
143
  - !ruby/object:Gem::Version
123
- version: '2.1'
144
+ version: '0'
124
145
  type: :development
125
146
  prerelease: false
126
147
  version_requirements: !ruby/object:Gem::Requirement
127
148
  requirements:
128
- - - "~>"
149
+ - - ">="
129
150
  - !ruby/object:Gem::Version
130
- version: '2.1'
151
+ version: '0'
131
152
  - !ruby/object:Gem::Dependency
132
153
  name: rake
133
154
  requirement: !ruby/object:Gem::Requirement
134
155
  requirements:
135
- - - "~>"
156
+ - - ">="
136
157
  - !ruby/object:Gem::Version
137
- version: '13.1'
158
+ version: '0'
138
159
  type: :development
139
160
  prerelease: false
140
161
  version_requirements: !ruby/object:Gem::Requirement
141
162
  requirements:
142
- - - "~>"
163
+ - - ">="
143
164
  - !ruby/object:Gem::Version
144
- version: '13.1'
165
+ version: '0'
145
166
  - !ruby/object:Gem::Dependency
146
- name: rubocop
167
+ name: standard
147
168
  requirement: !ruby/object:Gem::Requirement
148
169
  requirements:
149
- - - "~>"
170
+ - - ">="
150
171
  - !ruby/object:Gem::Version
151
- version: '1.59'
172
+ version: '0'
152
173
  type: :development
153
174
  prerelease: false
154
175
  version_requirements: !ruby/object:Gem::Requirement
155
176
  requirements:
156
- - - "~>"
177
+ - - ">="
157
178
  - !ruby/object:Gem::Version
158
- version: '1.59'
179
+ version: '0'
159
180
  - !ruby/object:Gem::Dependency
160
- name: rubocop-minitest
181
+ name: standard-custom
161
182
  requirement: !ruby/object:Gem::Requirement
162
183
  requirements:
163
- - - "~>"
184
+ - - ">="
164
185
  - !ruby/object:Gem::Version
165
- version: 0.33.0
186
+ version: '0'
166
187
  type: :development
167
188
  prerelease: false
168
189
  version_requirements: !ruby/object:Gem::Requirement
169
190
  requirements:
170
- - - "~>"
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ - !ruby/object:Gem::Dependency
195
+ name: standard-performance
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: m
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
171
220
  - !ruby/object:Gem::Version
172
- version: 0.33.0
173
- description:
174
- email:
221
+ version: '0'
222
+ - !ruby/object:Gem::Dependency
223
+ name: debug
224
+ requirement: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ type: :development
230
+ prerelease: false
231
+ version_requirements: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
175
236
  executables:
176
237
  - lenna
177
238
  extensions: []
178
239
  extra_rdoc_files: []
179
240
  files:
241
+ - ".devcontainer/Dockerfile"
242
+ - ".devcontainer/devcontainer.json"
243
+ - ".editorconfig"
244
+ - ".github/workflows/coverage.yaml"
245
+ - ".github/workflows/documentation.yaml"
246
+ - ".github/workflows/main.yaml"
247
+ - ".github/workflows/test.yaml"
248
+ - ".gitignore"
249
+ - ".standard.yml"
250
+ - ".tool-versions"
251
+ - LICENCE
252
+ - Rakefile
253
+ - benchmark/memory.png
254
+ - benchmark/rps.png
255
+ - benchmark/runtime_with_startup.png
256
+ - bin/console
257
+ - bin/release
258
+ - bin/setup
180
259
  - changelog.md
181
260
  - exe/lenna
261
+ - gems.rb
262
+ - guides/getting-started/readme.md
263
+ - guides/links.yaml
264
+ - guides/performance/readme.md
265
+ - guides/response/readme.md
266
+ - lennarb.gemspec
182
267
  - lib/lennarb.rb
183
- - lib/lennarb/plugin.rb
184
- - lib/lennarb/plugins/hooks.rb
185
- - lib/lennarb/plugins/mount.rb
268
+ - lib/lennarb/app.rb
269
+ - lib/lennarb/config.rb
270
+ - lib/lennarb/constansts.rb
271
+ - lib/lennarb/environment.rb
186
272
  - lib/lennarb/request.rb
273
+ - lib/lennarb/request_handler.rb
187
274
  - lib/lennarb/response.rb
188
275
  - lib/lennarb/route_node.rb
276
+ - lib/lennarb/routes.rb
189
277
  - lib/lennarb/version.rb
190
278
  - license.md
279
+ - logo/lennarb.png
191
280
  - readme.md
192
281
  homepage: https://aristotelesbr.github.io/lennarb
193
282
  licenses:
@@ -198,7 +287,6 @@ metadata:
198
287
  homepage_uri: https://aristotelesbr.github.io/lennarb
199
288
  rubygems_mfa_required: 'true'
200
289
  source_code_uri: https://github.com/aristotelesbr/lennarb
201
- post_install_message:
202
290
  rdoc_options: []
203
291
  require_paths:
204
292
  - lib
@@ -206,15 +294,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
294
  requirements:
207
295
  - - ">="
208
296
  - !ruby/object:Gem::Version
209
- version: '3.1'
297
+ version: '0'
210
298
  required_rubygems_version: !ruby/object:Gem::Requirement
211
299
  requirements:
212
300
  - - ">="
213
301
  - !ruby/object:Gem::Version
214
302
  version: '0'
215
303
  requirements: []
216
- rubygems_version: 3.5.23
217
- signing_key:
304
+ rubygems_version: 3.6.2
218
305
  specification_version: 4
219
306
  summary: Lennarb provides a lightweight yet robust solution for web routing in Ruby,
220
307
  focusing on performance and simplicity.
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lennarb
4
- module Plugin
5
- class Error < StandardError; end
6
-
7
- @registry = {}
8
- @defaults_loaded = false
9
-
10
- class << self
11
- attr_reader :registry
12
-
13
- def register(name, mod)
14
- registry[name.to_sym] = mod
15
- end
16
-
17
- def load(name)
18
- registry[name.to_sym] || raise(Error, "Plugin #{name} not found")
19
- end
20
-
21
- def load_defaults!
22
- return if @defaults_loaded
23
-
24
- # 1. Register default plugins
25
- plugins_path = File.expand_path('plugins', __dir__)
26
- load_plugins_from_directory(plugins_path)
27
-
28
- # # 2. Register custom plugins
29
- ENV.fetch('LENNARB_PLUGINS_PATH', nil)&.split(File::PATH_SEPARATOR)&.each do |path|
30
- load_plugins_from_directory(path)
31
- end
32
-
33
- @defaults_loaded = true
34
- end
35
-
36
- def load_defaults?
37
- ENV.fetch('LENNARB_AUTO_LOAD_DEFAULTS', 'true') == 'true'
38
- end
39
-
40
- private
41
-
42
- def load_plugins_from_directory(path)
43
- raise Error, "Plugin directory '#{path}' does not exist" unless File.directory?(path)
44
-
45
- Dir["#{path}/**/*.rb"].each { require _1 }
46
- end
47
- end
48
- end
49
- end