Skip to content

Commit

Permalink
chore: bump version to 1.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
aristotelesbr committed Nov 21, 2024
1 parent f94f2cd commit 9e02aac
Show file tree
Hide file tree
Showing 20 changed files with 1,189 additions and 1,098 deletions.
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
require 'rake/testtask'

Rake::TestTask.new do |t|
t.libs << 'test'
t.test_files = FileList['test/**/test_*.rb']
t.verbose = true
t.libs << 'test'
t.test_files = FileList['test/**/test_*.rb']
t.verbose = true
end

task default: :test
10 changes: 5 additions & 5 deletions bake/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
#
# @parameter test [String] the path to file
def test(test: nil)
test_dir = 'test'
test_dir = 'test'

all_tests_command = "Dir.glob(\"./#{test_dir}/**/test_*.rb\").each { require _1 }"
test_command = test ? "ruby -I#{test_dir} #{test}" : "ruby -I#{test_dir} -e '#{all_tests_command}'"
all_tests_command = "Dir.glob(\"./#{test_dir}/**/test_*.rb\").each { require _1 }"
test_command = test ? "ruby -I#{test_dir} #{test}" : "ruby -I#{test_dir} -e '#{all_tests_command}'"

stdout, _stderr, _status = Open3.capture3(test_command)
stdout, _stderr, _status = Open3.capture3(test_command)

puts stdout.green
puts stdout.green
end
76 changes: 38 additions & 38 deletions lennarb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,42 @@
require_relative 'lib/lennarb/version'

Gem::Specification.new do |spec|
spec.name = 'lennarb'
spec.version = Lennarb::VERSION

spec.summary = <<~DESC
Lennarb provides a lightweight yet robust solution for web routing in Ruby, focusing on performance and simplicity.
DESC
spec.authors = ['Aristóteles Coutinho']
spec.license = 'MIT'

spec.homepage = 'https://aristotelesbr.github.io/lennarb'

spec.metadata = {
'allowed_push_host' => 'https://rubygems.org',
'changelog_uri' => 'https://github.com/aristotelesbr/lennarb/blob/master/changelog.md',
'homepage_uri' => 'https://aristotelesbr.github.io/lennarb',
'rubygems_mfa_required' => 'true',
'source_code_uri' => 'https://github.com/aristotelesbr/lennarb'
}

spec.files = Dir['{exe,lib}/**/*', '*.md', base: __dir__]

spec.bindir = 'exe'
spec.executables = ['lenna']

spec.required_ruby_version = '>= 3.1'

spec.add_dependency 'colorize', '~> 1.1'
spec.add_dependency 'rack', '~> 3.0', '>= 3.0.8'

spec.add_development_dependency 'bake', '~> 0.18.2'
spec.add_development_dependency 'bundler', '~> 2.2'
spec.add_development_dependency 'covered', '~> 0.25.1'
spec.add_development_dependency 'minitest', '~> 5.20'
spec.add_development_dependency 'puma', '~> 6.4'
spec.add_development_dependency 'rack-test', '~> 2.1'
spec.add_development_dependency 'rake', '~> 13.1'
spec.add_development_dependency 'rubocop', '~> 1.59'
spec.add_development_dependency 'rubocop-minitest', '~> 0.33.0'
spec.name = 'lennarb'
spec.version = Lennarb::VERSION

spec.summary = <<~DESC
Lennarb provides a lightweight yet robust solution for web routing in Ruby, focusing on performance and simplicity.
DESC
spec.authors = ['Aristóteles Coutinho']
spec.license = 'MIT'

spec.homepage = 'https://aristotelesbr.github.io/lennarb'

spec.metadata = {
'allowed_push_host' => 'https://rubygems.org',
'changelog_uri' => 'https://github.com/aristotelesbr/lennarb/blob/master/changelog.md',
'homepage_uri' => 'https://aristotelesbr.github.io/lennarb',
'rubygems_mfa_required' => 'true',
'source_code_uri' => 'https://github.com/aristotelesbr/lennarb'
}

spec.files = Dir['{exe,lib}/**/*', '*.md', base: __dir__]

spec.bindir = 'exe'
spec.executables = ['lenna']

spec.required_ruby_version = '>= 3.1'

spec.add_dependency 'colorize', '~> 1.1'
spec.add_dependency 'rack', '~> 3.0', '>= 3.0.8'

spec.add_development_dependency 'bake', '~> 0.18.2'
spec.add_development_dependency 'bundler', '~> 2.2'
spec.add_development_dependency 'covered', '~> 0.25.1'
spec.add_development_dependency 'minitest', '~> 5.20'
spec.add_development_dependency 'puma', '~> 6.4'
spec.add_development_dependency 'rack-test', '~> 2.1'
spec.add_development_dependency 'rake', '~> 13.1'
spec.add_development_dependency 'rubocop', '~> 1.59'
spec.add_development_dependency 'rubocop-minitest', '~> 0.33.0'
end
262 changes: 138 additions & 124 deletions lib/lennarb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,137 +8,151 @@
require 'pathname'
require 'rack'

# Base class for Lennarb
#
require_relative 'lennarb/application/base'
require_relative 'lennarb/plugin'
require_relative 'lennarb/request'
require_relative 'lennarb/response'
require_relative 'lennarb/route_node'
require_relative 'lennarb/version'

class Lennarb
# Error class
#
class LennarbError < StandardError; end

# @attribute [r] root
# @returns [RouteNode]
#
attr_reader :_root

# @attribute [r] applied_plugins
# @returns [Array]
#
attr_reader :_applied_plugins

# Initialize the application
#
# @yield { ... } The application
#
# @returns [Lennarb]
#
def initialize
@_root = RouteNode.new
@_applied_plugins = []
yield self if block_given?
end

# Split a path into parts
#
# @parameter [String] path
#
# @returns [Array] parts. Ex. ['users', ':id']
#
SplitPath = ->(path) { path.split('/').reject(&:empty?) }
private_constant :SplitPath

# Call the application
#
# @parameter [Hash] env
#
# @returns [Array] response
#
def call(env)
http_method = env[Rack::REQUEST_METHOD].to_sym
parts = SplitPath[env[Rack::PATH_INFO]]

block, params = @_root.match_route(parts, http_method)
return [404, { 'content-type' => 'text/plain' }, ['Not Found']] unless block

@res = Response.new
req = Request.new(env, params)

catch(:halt) do
instance_exec(req, @res, &block)
@res.finish
end
end

# Freeze the routes
#
# @returns [void]
#
def freeze! = @_root.freeze

# Add a routes
#
# @parameter [String] path
# @parameter [Proc] block
#
# @returns [void]
#
def get(path, &block) = add_route(path, :GET, block)
def put(path, &block) = add_route(path, :PUT, block)
def post(path, &block) = add_route(path, :POST, block)
def head(path, &block) = add_route(path, :HEAD, block)
def patch(path, &block) = add_route(path, :PATCH, block)
def delete(path, &block) = add_route(path, :DELETE, block)
def options(path, &block) = add_route(path, :OPTIONS, block)

# Add plugin to extend the router
#
# @parameter [String] plugin_name
# @parameter [args] *args
# @parameter [Block] block
#
# @returns [void]
#
def plugin(plugin_name, *, &)
return if @_applied_plugins.include?(plugin_name)

plugin_module = Plugin.load(plugin_name)
extend plugin_module::InstanceMethods if defined?(plugin_module::InstanceMethods)
self.class.extend plugin_module::ClassMethods if defined?(plugin_module::ClassMethods)
plugin_module.setup(self.class, *, &) if plugin_module.respond_to?(:setup)

@_applied_plugins << plugin_name
end

# Merge the other RouteNode into the current one
#
# @parameter other [RouteNode] The other RouteNode to merge into the current one
#
# @return [void]
#
def merge!(other)
raise "Expected a Lennarb instance, got #{other.class}" unless other.is_a?(Lennarb)

@_root.merge!(other._root)
class LennarbError < StandardError; end

attr_reader :_root, :_plugins, :_loaded_plugins, :_middlewares, :_app

def self.use(middleware, *args, &block)
@_middlewares ||= []
@_middlewares << [middleware, args, block]
end

def self.get(path, &block) = add_route(path, :GET, block)
def self.put(path, &block) = add_route(path, :PUT, block)
def self.post(path, &block) = add_route(path, :POST, block)
def self.head(path, &block) = add_route(path, :HEAD, block)
def self.patch(path, &block) = add_route(path, :PATCH, block)
def self.delete(path, &block) = add_route(path, :DELETE, block)
def self.options(path, &block) = add_route(path, :OPTIONS, block)

def self.inherited(subclass)
super
subclass.instance_variable_set(:@_root, RouteNode.new)
subclass.instance_variable_set(:@_plugins, [])
subclass.instance_variable_set(:@_middlewares, @_middlewares&.dup || [])

Plugin.load_defaults! if Plugin.load_defaults?
end

def self.plugin(plugin_name, *, &)
@_loaded_plugins ||= {}
@_plugins ||= []

return if @_loaded_plugins.key?(plugin_name)

plugin_module = Plugin.load(plugin_name)
plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)

@_loaded_plugins[plugin_name] = plugin_module
@_plugins << plugin_name
end

def self.freeze!
app = new
app.freeze!
app
end

def self.add_route(path, http_method, block)
@_root ||= RouteNode.new
parts = path.split('/').reject(&:empty?)
@_root.add_route(parts, http_method, block)
end

private_class_method :add_route

def initialize
@_mutex = Mutex.new
@_root = self.class.instance_variable_get(:@_root)&.dup || RouteNode.new
@_plugins = self.class.instance_variable_get(:@_plugins)&.dup || []
@_loaded_plugins = self.class.instance_variable_get(:@_loaded_plugins)&.dup || {}
@_middlewares = self.class.instance_variable_get(:@_middlewares)&.dup || []

build_app

yield self if block_given?
end

private

# Add a route
#
# @parameter [String] path
# @parameter [String] http_method
# @parameter [Proc] block
#
# @returns [void]
#
def add_route(path, http_method, block)
parts = SplitPath[path]
@_root.add_route(parts, http_method, block)
end
def call(env) = @_mutex.synchronize { @_app.call(env) }

def freeze!
return self if @_mounted

@_root.freeze
@_plugins.freeze
@_loaded_plugins.freeze
@_middlewares.freeze
@_app.freeze if @_app.respond_to?(:freeze)
self
end

def get(path, &block) = add_route(path, :GET, block)
def put(path, &block) = add_route(path, :PUT, block)
def post(path, &block) = add_route(path, :POST, block)
def head(path, &block) = add_route(path, :HEAD, block)
def patch(path, &block) = add_route(path, :PATCH, block)
def delete(path, &block) = add_route(path, :DELETE, block)
def options(path, &block) = add_route(path, :OPTIONS, block)

def plugin(plugin_name, *, &)
return if @_loaded_plugins.key?(plugin_name)

plugin_module = Plugin.load(plugin_name)
self.class.extend plugin_module::ClassMethods if plugin_module.const_defined?(:ClassMethods)
self.class.include plugin_module::InstanceMethods if plugin_module.const_defined?(:InstanceMethods)
plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)
@_loaded_plugins[plugin_name] = plugin_module
@_plugins << plugin_name
end

private

def build_app
@_app = method(:process_request)

@_middlewares.reverse_each do |middleware, args, block|
@_app = middleware.new(@_app, *args, &block)
end
end

def process_request(env)
http_method = env[Rack::REQUEST_METHOD].to_sym
parts = env[Rack::PATH_INFO].split('/').reject(&:empty?)

block, params = @_root.match_route(parts, http_method)
return not_found unless block

res = Response.new
req = Request.new(env, params)

catch(:halt) do
instance_exec(req, res, &block)
res.finish
end
rescue StandardError => e
handle_error(e)
end

def handle_error(error)
case error
when ArgumentError
[400, { 'content-type' => 'text/plain' }, ["Bad Request: #{error.message}"]]
else
[500, { 'content-type' => 'text/plain' }, ["Internal Server Error: #{error.message}"]]
end
end

def not_found = [404, { 'content-type' => 'text/plain' }, ['Not Found']]

def add_route(path, http_method, block)
parts = path.split('/').reject(&:empty?)
@_root.add_route(parts, http_method, block)
end
end
Loading

0 comments on commit 9e02aac

Please sign in to comment.