lennarb 0.1.7 → 0.2.0
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 +4 -4
- data/changelog.md +21 -0
- data/exe/lenna +2 -4
- data/lib/lennarb/request.rb +37 -0
- data/lib/lennarb/response.rb +138 -0
- data/lib/lennarb/route_node.rb +73 -0
- data/lib/lennarb/version.rb +3 -3
- data/lib/lennarb.rb +78 -27
- data/license.md +2 -1
- data/readme.md +30 -13
- metadata +42 -57
- data/lib/lenna/application.rb +0 -53
- data/lib/lenna/cli/app.rb +0 -39
- data/lib/lenna/cli/commands/create_project.rb +0 -125
- data/lib/lenna/cli/commands/interface.rb +0 -20
- data/lib/lenna/cli/commands/start_server.rb +0 -143
- data/lib/lenna/cli/templates/application.erb +0 -11
- data/lib/lenna/cli/templates/config.ru.erb +0 -5
- data/lib/lenna/cli/templates/gemfile.erb +0 -14
- data/lib/lenna/middleware/app.rb +0 -118
- data/lib/lenna/middleware/default/error_handler.rb +0 -243
- data/lib/lenna/middleware/default/logging.rb +0 -93
- data/lib/lenna/middleware/default/reload.rb +0 -97
- data/lib/lenna/router/builder.rb +0 -124
- data/lib/lenna/router/cache.rb +0 -52
- data/lib/lenna/router/namespace_stack.rb +0 -77
- data/lib/lenna/router/request.rb +0 -141
- data/lib/lenna/router/response.rb +0 -509
- data/lib/lenna/router/route_matcher.rb +0 -68
- data/lib/lenna/router.rb +0 -206
- data/lib/lennarb/array_extensions.rb +0 -31
data/lib/lenna/application.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
# Internal dependencies
|
7
|
-
#
|
8
|
-
require 'lenna/middleware/default/error_handler'
|
9
|
-
require 'lenna/middleware/default/logging'
|
10
|
-
require 'lenna/router'
|
11
|
-
|
12
|
-
# The Lenna module is used to namespace the framework.
|
13
|
-
#
|
14
|
-
# @public
|
15
|
-
#
|
16
|
-
module Lenna
|
17
|
-
# The base class is used to start the server.
|
18
|
-
#
|
19
|
-
# @public
|
20
|
-
#
|
21
|
-
class Application < Router
|
22
|
-
# Initialize the base class
|
23
|
-
#
|
24
|
-
# @yield { ... } the block to be evaluated in the context of the instance.
|
25
|
-
#
|
26
|
-
# @return [void | Application] Returns the instance if a block is given.
|
27
|
-
#
|
28
|
-
def initialize
|
29
|
-
super
|
30
|
-
yield self if block_given?
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# The base module is used to include the base class.
|
35
|
-
#
|
36
|
-
# @public
|
37
|
-
#
|
38
|
-
module Base
|
39
|
-
def self.included(base)
|
40
|
-
base.extend(ClassMethods)
|
41
|
-
end
|
42
|
-
|
43
|
-
module ClassMethods
|
44
|
-
# Initialize the base module
|
45
|
-
#
|
46
|
-
# @return [Lenna::Application] Returns the instance.
|
47
|
-
#
|
48
|
-
# @public
|
49
|
-
#
|
50
|
-
def app = @app ||= Lenna::Application.new
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
data/lib/lenna/cli/app.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
module Lenna
|
7
|
-
module Cli
|
8
|
-
# Mediator class for CLI
|
9
|
-
#
|
10
|
-
# @private `Since v0.1.0`
|
11
|
-
#
|
12
|
-
module App
|
13
|
-
extend self
|
14
|
-
# Execute the command
|
15
|
-
#
|
16
|
-
# @return [void]
|
17
|
-
#
|
18
|
-
def run!(args)
|
19
|
-
subcommand = args.shift
|
20
|
-
|
21
|
-
strategy = parse_options(subcommand, args)
|
22
|
-
|
23
|
-
strategy.is_a?(Lenna::Cli::Commands::Interface) or fail ::ArgumentError
|
24
|
-
|
25
|
-
strategy.call
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def parse_options(command, args)
|
31
|
-
case command
|
32
|
-
in 'new' | 'start' then Lenna::Cli::Commands::CreateProject.new(args)
|
33
|
-
in 'server' | 's' then Lenna::Cli::Commands::StartServer.new(args)
|
34
|
-
else 'help'
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,125 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
require 'console'
|
7
|
-
require 'erb'
|
8
|
-
require 'fileutils'
|
9
|
-
|
10
|
-
module Lenna
|
11
|
-
module Cli
|
12
|
-
module Commands
|
13
|
-
# Command for creating a new app
|
14
|
-
#
|
15
|
-
# @private `Since v0.1.0`
|
16
|
-
#
|
17
|
-
class CreateProject
|
18
|
-
include ::Lenna::Cli::Commands::Interface
|
19
|
-
|
20
|
-
# @!attribute [r] app_name
|
21
|
-
#
|
22
|
-
private attr_accessor :app_name
|
23
|
-
|
24
|
-
# Initialize the command
|
25
|
-
#
|
26
|
-
# @parameter app_name [Array<String>] The name of the app
|
27
|
-
#
|
28
|
-
def initialize(app_name)
|
29
|
-
self.app_name = app_name[0]
|
30
|
-
end
|
31
|
-
|
32
|
-
# Execute the command
|
33
|
-
#
|
34
|
-
# @parameter app_name [String] The name of the app
|
35
|
-
#
|
36
|
-
# @return [void]
|
37
|
-
#
|
38
|
-
def call
|
39
|
-
return puts 'Please specify an app name'.red if app_name.nil?
|
40
|
-
|
41
|
-
create_app(app_name) do
|
42
|
-
create_gemfile
|
43
|
-
create_config_ru
|
44
|
-
create_app_directory
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
# Create a directory for the app
|
51
|
-
#
|
52
|
-
# @parameter app_name [String] The name of the app
|
53
|
-
#
|
54
|
-
# @yield { ... } The block to be executed after the directory
|
55
|
-
#
|
56
|
-
# @return [void]
|
57
|
-
#
|
58
|
-
def create_app(app_name)
|
59
|
-
::Console.info("Creating a new app named #{app_name}")
|
60
|
-
|
61
|
-
::FileUtils.mkdir_p(app_name)
|
62
|
-
|
63
|
-
::FileUtils.cd(app_name).tap { yield app_name } if block_given?
|
64
|
-
|
65
|
-
app_name
|
66
|
-
end
|
67
|
-
|
68
|
-
# Create a new Gemfile for the app. This will be use template
|
69
|
-
# file in the `templates` directory.
|
70
|
-
#
|
71
|
-
# @parameter app_name [String] The name of the app
|
72
|
-
#
|
73
|
-
# @return [void]
|
74
|
-
#
|
75
|
-
def create_gemfile
|
76
|
-
{ version: Lennarb::VERSION }.then { create_template('gemfile', _1, 'Gemfile') }
|
77
|
-
end
|
78
|
-
|
79
|
-
# Create a new config.ru for the app. This will be use template
|
80
|
-
# file in the `templates` directory.
|
81
|
-
#
|
82
|
-
# @return [void]
|
83
|
-
#
|
84
|
-
def create_config_ru
|
85
|
-
create_template('config.ru', {})
|
86
|
-
end
|
87
|
-
|
88
|
-
# Create a new application.rb for the app. This will be use template
|
89
|
-
# file in the `templates` directory.
|
90
|
-
#
|
91
|
-
# @return [void]
|
92
|
-
#
|
93
|
-
# @See #create_template
|
94
|
-
# @See lenna/cli/templates/application
|
95
|
-
#
|
96
|
-
def create_app_directory
|
97
|
-
simple_template = <<~HTML.strip
|
98
|
-
'<h2>Hello, welcome to Lenna! #{Lennarb::VERSION}</h1>'
|
99
|
-
HTML
|
100
|
-
create_template('application', { simple_template: }, 'app/application.rb')
|
101
|
-
end
|
102
|
-
|
103
|
-
# Method for creating file based on a template
|
104
|
-
#
|
105
|
-
# @parameter template_name [String] The name of the template
|
106
|
-
# @parameter template_data [Hash] The data to be used in the template
|
107
|
-
#
|
108
|
-
# @return [void]
|
109
|
-
#
|
110
|
-
def create_template(template_name, template_data, file_name = template_name)
|
111
|
-
Lennarb # rubocop:disable Lint
|
112
|
-
.root
|
113
|
-
.join("lib/lenna/cli/templates/#{template_name}.erb")
|
114
|
-
.then { ::File.read(_1) }
|
115
|
-
.then { ::ERB.new(_1).result_with_hash(template_data) }
|
116
|
-
.then do |content|
|
117
|
-
return ::File.write(file_name, content) unless file_name.include?('/')
|
118
|
-
|
119
|
-
::FileUtils.mkdir_p(::File.dirname(file_name)) && ::File.write(file_name, content)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
module Lenna
|
7
|
-
module Cli
|
8
|
-
module Commands
|
9
|
-
module Interface
|
10
|
-
def new(args)
|
11
|
-
raise NotImplementedError
|
12
|
-
end
|
13
|
-
|
14
|
-
def call
|
15
|
-
raise NotImplementedError
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,143 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
require 'console'
|
7
|
-
require 'optparse'
|
8
|
-
|
9
|
-
module Lenna
|
10
|
-
module Cli
|
11
|
-
module Commands
|
12
|
-
# Command for creating a new app
|
13
|
-
#
|
14
|
-
# @private `Since v0.1.0`
|
15
|
-
#
|
16
|
-
class StartServer
|
17
|
-
include Lenna::Cli::Commands::Interface
|
18
|
-
|
19
|
-
# @!attribute [rw] port
|
20
|
-
# @return [Integer] Port to start the server
|
21
|
-
#
|
22
|
-
private attr_accessor :port
|
23
|
-
# @!attribute [rw] server
|
24
|
-
# @return [String] Server to start
|
25
|
-
#
|
26
|
-
private attr_accessor :server
|
27
|
-
# @!attribute [rw] command
|
28
|
-
# @return [String] Command to execute
|
29
|
-
#
|
30
|
-
private attr_accessor :command
|
31
|
-
|
32
|
-
def initialize(args)
|
33
|
-
self.command = args.shift
|
34
|
-
|
35
|
-
options = parse_options!(args)
|
36
|
-
|
37
|
-
self.port = options[:port]
|
38
|
-
self.server = options[:server]
|
39
|
-
end
|
40
|
-
|
41
|
-
# Execute the command
|
42
|
-
#
|
43
|
-
# @return [void]
|
44
|
-
#
|
45
|
-
def call
|
46
|
-
::Console.debug("Starting server on port #{port}...")
|
47
|
-
|
48
|
-
case server
|
49
|
-
in 'puma' | 'falcon' => server then start_server(port:, server:)
|
50
|
-
else fail ::ArgumentError, ::Console.error("The server '#{server}' is not supported yet.")
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
# Parse the options
|
57
|
-
#
|
58
|
-
# @parameter args [Array<String>] The arguments
|
59
|
-
#
|
60
|
-
# @return [Hash] The options
|
61
|
-
#
|
62
|
-
def parse_options!(args)
|
63
|
-
options = { port: 4000, server: 'puma' }
|
64
|
-
|
65
|
-
::OptionParser.new do |opts|
|
66
|
-
opts.banner = "Usage: lenna #{command} [options]"
|
67
|
-
|
68
|
-
opts.on('-p', '--port [PORT]', Integer, 'Port to start the server') do |port|
|
69
|
-
options[:port] = port
|
70
|
-
end
|
71
|
-
|
72
|
-
opts.on('-s', '--server [SERVER]', String, 'Server to start') do |server|
|
73
|
-
options[:server] = server
|
74
|
-
end
|
75
|
-
end.parse!(args)
|
76
|
-
|
77
|
-
options
|
78
|
-
end
|
79
|
-
|
80
|
-
# Start the server
|
81
|
-
#
|
82
|
-
# @paramaeter port [Integer] Port to start the server
|
83
|
-
# @paramaeter server [String] Server to start
|
84
|
-
#
|
85
|
-
# @return [void]
|
86
|
-
#
|
87
|
-
def start_server(port:, server:)
|
88
|
-
return warn_not_installed(server) unless instaled_gem?(server)
|
89
|
-
|
90
|
-
::File.exist?(config_file) or fail ::StandardError, ::Console.error("'config.ru' not found in #{poroject_name}.")
|
91
|
-
|
92
|
-
system("bundle exec #{server} #{config_file} --port #{port}", chdir: current_path)
|
93
|
-
rescue ::ArgumentError
|
94
|
-
::Console.error("The server '#{server}' is not supported yet.")
|
95
|
-
rescue ::Interrupt
|
96
|
-
::Console.info("\nServer stopped.")
|
97
|
-
rescue ::StandardError => e
|
98
|
-
::Console.error(self, e.message)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Check if the gem is installed
|
102
|
-
#
|
103
|
-
# @parameter name [String] Name of the gem
|
104
|
-
#
|
105
|
-
# @return [Boolean]
|
106
|
-
#
|
107
|
-
def instaled_gem?(name)
|
108
|
-
::Gem::Specification.find_by_name(name)
|
109
|
-
rescue ::Gem::LoadError
|
110
|
-
false
|
111
|
-
end
|
112
|
-
|
113
|
-
# Get the name of the project
|
114
|
-
#
|
115
|
-
# @return [String] Name of the project
|
116
|
-
#
|
117
|
-
def poroject_name = @current_path.split('/').last
|
118
|
-
|
119
|
-
# Get the path of the project
|
120
|
-
#
|
121
|
-
# @return [String] Path of the project
|
122
|
-
#
|
123
|
-
def current_path = @current_path ||= ::Dir.pwd
|
124
|
-
|
125
|
-
# Get the path of the config file
|
126
|
-
#
|
127
|
-
# @return [String] Path of the config file
|
128
|
-
#
|
129
|
-
def config_file = "#{current_path}/config.ru"
|
130
|
-
|
131
|
-
# Warn the user that the gem is not installed
|
132
|
-
#
|
133
|
-
# @parameter name [String] Name of the gem
|
134
|
-
#
|
135
|
-
# @return [void]
|
136
|
-
#
|
137
|
-
def warn_not_installed(name)
|
138
|
-
::Console.warn("The gem '#{name}' is not installed. Please install it and try again.")
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
source 'https://rubygems.org'
|
4
|
-
|
5
|
-
# [https://rubygems.org/gems/lennarb]
|
6
|
-
# Lenna is a lightweight and experimental web framework for Ruby. It's designed
|
7
|
-
# to be modular and easy to use. Also, that's how I affectionately call my wife.
|
8
|
-
gem 'lennarb', '~> <%= version %>'
|
9
|
-
# [https://rubygems.org/gems/puma]
|
10
|
-
# Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for Ruby/Rack applications.
|
11
|
-
gem 'puma', '~> 6.4'
|
12
|
-
|
13
|
-
group :development, :test do
|
14
|
-
end
|
data/lib/lenna/middleware/app.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
-
|
6
|
-
require 'singleton'
|
7
|
-
|
8
|
-
module Lenna
|
9
|
-
module Middleware
|
10
|
-
# The MiddlewareManager class is responsible for managing the middlewares.
|
11
|
-
#
|
12
|
-
# @attr global_middlewares [Array] the global middlewares
|
13
|
-
# @attr middleware_chains_cache [Hash] the middleware chains cache
|
14
|
-
#
|
15
|
-
# Middleware chains are cached by action.
|
16
|
-
# The middlewares that are added to a specific route are added to the
|
17
|
-
# global middlewares.
|
18
|
-
#
|
19
|
-
# @private Since `v0.1.0`
|
20
|
-
#
|
21
|
-
class App
|
22
|
-
include Singleton
|
23
|
-
|
24
|
-
# @return [Array] the global middlewares.
|
25
|
-
#
|
26
|
-
attr_accessor :global_middlewares
|
27
|
-
# @return [Hash] the middleware chains cache.
|
28
|
-
#
|
29
|
-
attr_accessor :middleware_chains_cache
|
30
|
-
|
31
|
-
# This method will initialize the global middlewares and the
|
32
|
-
# middleware chains cache.
|
33
|
-
#
|
34
|
-
# @return [void]
|
35
|
-
#
|
36
|
-
def initialize
|
37
|
-
@global_middlewares = []
|
38
|
-
@middleware_chains_cache = {}
|
39
|
-
end
|
40
|
-
|
41
|
-
# This method is used to reset the global middlewares and the middleware
|
42
|
-
# chains cache.
|
43
|
-
#
|
44
|
-
# @return [void]
|
45
|
-
#
|
46
|
-
def reset!
|
47
|
-
@global_middlewares = []
|
48
|
-
@middleware_chains_cache = {}
|
49
|
-
end
|
50
|
-
|
51
|
-
# This method is used to add a middleware to the global middlewares.
|
52
|
-
# @parameter middlewares [Array] the middlewares to be used
|
53
|
-
# @return [void]
|
54
|
-
#
|
55
|
-
def use(middlewares)
|
56
|
-
@global_middlewares += Array(middlewares)
|
57
|
-
@middleware_chains_cache = {}
|
58
|
-
end
|
59
|
-
|
60
|
-
# This method is used to fetch or build the middleware chain for the given
|
61
|
-
# action and route middlewares.
|
62
|
-
#
|
63
|
-
# @parameter action [Proc] the action to be executed
|
64
|
-
# @parameter route_middlewares [Array] the middlewares to be used
|
65
|
-
# @return [Proc] the middleware chain
|
66
|
-
#
|
67
|
-
#
|
68
|
-
def fetch_or_build_middleware_chain(
|
69
|
-
action,
|
70
|
-
route_middlewares,
|
71
|
-
http_method: nil,
|
72
|
-
path: nil
|
73
|
-
)
|
74
|
-
signature =
|
75
|
-
if http_method && path
|
76
|
-
[http_method, path, route_middlewares].hash.to_s
|
77
|
-
else
|
78
|
-
['global', route_middlewares].hash.to_s
|
79
|
-
end
|
80
|
-
|
81
|
-
@middleware_chains_cache[signature] ||=
|
82
|
-
build_middleware_chain(action, route_middlewares)
|
83
|
-
end
|
84
|
-
|
85
|
-
# This method is used to build the middleware chain for the given action
|
86
|
-
# and middlewares.
|
87
|
-
#
|
88
|
-
# @parameter action [Proc] the action to be executed
|
89
|
-
# @parameter middlewares [Array] the middlewares to be used
|
90
|
-
# @return [Proc] the middleware chain
|
91
|
-
#
|
92
|
-
# ex.
|
93
|
-
# Given the action:
|
94
|
-
# `->(req, res) { res << 'Hello' }` and the
|
95
|
-
# middlewares [mw1, mw2], the middleware
|
96
|
-
# chain will be:
|
97
|
-
# mw1 -> mw2 -> action
|
98
|
-
# The action will be the last middleware in the
|
99
|
-
# chain.
|
100
|
-
#
|
101
|
-
def build_middleware_chain(action, middlewares)
|
102
|
-
all_middlewares = (@global_middlewares + Array(middlewares))
|
103
|
-
|
104
|
-
all_middlewares.reverse.reduce(action) do |next_middleware, middleware|
|
105
|
-
->(req, res) {
|
106
|
-
middleware.call(
|
107
|
-
req,
|
108
|
-
res,
|
109
|
-
-> {
|
110
|
-
next_middleware.call(req, res)
|
111
|
-
}
|
112
|
-
)
|
113
|
-
}
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|