tarsier 0.1.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 +7 -0
- data/CHANGELOG.md +175 -0
- data/LICENSE.txt +21 -0
- data/README.md +984 -0
- data/exe/tarsier +7 -0
- data/lib/tarsier/application.rb +336 -0
- data/lib/tarsier/cli/commands/console.rb +87 -0
- data/lib/tarsier/cli/commands/generate.rb +85 -0
- data/lib/tarsier/cli/commands/help.rb +50 -0
- data/lib/tarsier/cli/commands/new.rb +59 -0
- data/lib/tarsier/cli/commands/routes.rb +139 -0
- data/lib/tarsier/cli/commands/server.rb +123 -0
- data/lib/tarsier/cli/commands/version.rb +14 -0
- data/lib/tarsier/cli/generators/app.rb +528 -0
- data/lib/tarsier/cli/generators/base.rb +93 -0
- data/lib/tarsier/cli/generators/controller.rb +91 -0
- data/lib/tarsier/cli/generators/middleware.rb +81 -0
- data/lib/tarsier/cli/generators/migration.rb +109 -0
- data/lib/tarsier/cli/generators/model.rb +109 -0
- data/lib/tarsier/cli/generators/resource.rb +27 -0
- data/lib/tarsier/cli/loader.rb +18 -0
- data/lib/tarsier/cli.rb +46 -0
- data/lib/tarsier/controller.rb +282 -0
- data/lib/tarsier/database.rb +588 -0
- data/lib/tarsier/errors.rb +77 -0
- data/lib/tarsier/middleware/base.rb +47 -0
- data/lib/tarsier/middleware/compression.rb +113 -0
- data/lib/tarsier/middleware/cors.rb +101 -0
- data/lib/tarsier/middleware/csrf.rb +88 -0
- data/lib/tarsier/middleware/logger.rb +74 -0
- data/lib/tarsier/middleware/rate_limit.rb +110 -0
- data/lib/tarsier/middleware/stack.rb +143 -0
- data/lib/tarsier/middleware/static.rb +124 -0
- data/lib/tarsier/model.rb +590 -0
- data/lib/tarsier/params.rb +269 -0
- data/lib/tarsier/query.rb +495 -0
- data/lib/tarsier/request.rb +274 -0
- data/lib/tarsier/response.rb +282 -0
- data/lib/tarsier/router/compiler.rb +173 -0
- data/lib/tarsier/router/node.rb +97 -0
- data/lib/tarsier/router/route.rb +119 -0
- data/lib/tarsier/router.rb +272 -0
- data/lib/tarsier/version.rb +5 -0
- data/lib/tarsier/websocket.rb +275 -0
- data/lib/tarsier.rb +167 -0
- data/sig/tarsier.rbs +485 -0
- metadata +230 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class Routes
|
|
7
|
+
HELP = <<~HELP
|
|
8
|
+
Usage: tarsier routes [options]
|
|
9
|
+
|
|
10
|
+
Display all routes defined in your application.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-g, --grep PATTERN Filter routes by pattern
|
|
14
|
+
--json Output routes as JSON
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
tarsier routes
|
|
18
|
+
tarsier routes -g users
|
|
19
|
+
tarsier routes --json
|
|
20
|
+
HELP
|
|
21
|
+
|
|
22
|
+
def self.show_help
|
|
23
|
+
puts HELP
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.run(args)
|
|
27
|
+
options = parse_options(args)
|
|
28
|
+
|
|
29
|
+
app = load_application
|
|
30
|
+
unless app
|
|
31
|
+
puts "Error: Could not load application"
|
|
32
|
+
exit 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
routes = app.routes_list
|
|
36
|
+
routes = filter_routes(routes, options[:grep]) if options[:grep]
|
|
37
|
+
|
|
38
|
+
if options[:json]
|
|
39
|
+
output_json(routes)
|
|
40
|
+
else
|
|
41
|
+
output_table(routes)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.parse_options(args)
|
|
46
|
+
options = { grep: nil, json: false }
|
|
47
|
+
|
|
48
|
+
i = 0
|
|
49
|
+
while i < args.length
|
|
50
|
+
case args[i]
|
|
51
|
+
when "-g", "--grep"
|
|
52
|
+
options[:grep] = args[i + 1]
|
|
53
|
+
i += 2
|
|
54
|
+
when "--json"
|
|
55
|
+
options[:json] = true
|
|
56
|
+
i += 1
|
|
57
|
+
else
|
|
58
|
+
i += 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
options
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.load_application
|
|
66
|
+
if File.exist?("config/application.rb")
|
|
67
|
+
require "./config/application"
|
|
68
|
+
elsif File.exist?("app.rb")
|
|
69
|
+
require "./app"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Tarsier.app
|
|
73
|
+
rescue LoadError, StandardError => e
|
|
74
|
+
puts "Warning: #{e.message}"
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.filter_routes(routes, pattern)
|
|
79
|
+
regex = Regexp.new(pattern, Regexp::IGNORECASE)
|
|
80
|
+
routes.select { |r| r.path.match?(regex) || r.name&.to_s&.match?(regex) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.output_table(routes)
|
|
84
|
+
puts
|
|
85
|
+
puts "Application Routes"
|
|
86
|
+
puts "=" * 70
|
|
87
|
+
|
|
88
|
+
if routes.empty?
|
|
89
|
+
puts "No routes defined"
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Calculate column widths
|
|
94
|
+
method_width = routes.map { |r| r.method.to_s.length }.max
|
|
95
|
+
path_width = routes.map { |r| r.path.length }.max
|
|
96
|
+
name_width = routes.map { |r| r.name.to_s.length }.max
|
|
97
|
+
|
|
98
|
+
# Header
|
|
99
|
+
puts format("%-#{method_width}s %-#{path_width}s %-#{name_width}s %s",
|
|
100
|
+
"Method", "Path", "Name", "Handler")
|
|
101
|
+
puts "-" * 70
|
|
102
|
+
|
|
103
|
+
# Routes
|
|
104
|
+
routes.each do |route|
|
|
105
|
+
handler = format_handler(route)
|
|
106
|
+
puts format("%-#{method_width}s %-#{path_width}s %-#{name_width}s %s",
|
|
107
|
+
route.method, route.path, route.name || "", handler)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
puts
|
|
111
|
+
puts "Total: #{routes.size} routes"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.output_json(routes)
|
|
115
|
+
require "json"
|
|
116
|
+
data = routes.map do |route|
|
|
117
|
+
{
|
|
118
|
+
method: route.method,
|
|
119
|
+
path: route.path,
|
|
120
|
+
name: route.name,
|
|
121
|
+
handler: format_handler(route)
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
puts JSON.pretty_generate(data)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.format_handler(route)
|
|
128
|
+
if route.proc_handler?
|
|
129
|
+
"Proc"
|
|
130
|
+
elsif route.controller_class
|
|
131
|
+
"#{route.controller_class}##{route.action_name}"
|
|
132
|
+
else
|
|
133
|
+
route.handler.to_s
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class Server
|
|
7
|
+
HELP = <<~HELP
|
|
8
|
+
Usage: tarsier server [options]
|
|
9
|
+
|
|
10
|
+
Start the Tarsier development server.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-p, --port PORT Port to listen on (default: 7827)
|
|
14
|
+
-b, --binding HOST Host to bind to (default: 0.0.0.0)
|
|
15
|
+
-e, --env ENV Environment (default: development)
|
|
16
|
+
--no-reload Disable auto-reload
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
tarsier server
|
|
20
|
+
tarsier server -p 4000
|
|
21
|
+
tarsier server -e production
|
|
22
|
+
HELP
|
|
23
|
+
|
|
24
|
+
def self.show_help
|
|
25
|
+
puts HELP
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.run(args)
|
|
29
|
+
options = parse_options(args)
|
|
30
|
+
|
|
31
|
+
unless File.exist?("config.ru") || File.exist?("app.rb")
|
|
32
|
+
puts "Error: No Tarsier application found in current directory"
|
|
33
|
+
puts "Make sure you're in a Tarsier project directory"
|
|
34
|
+
exit 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
start_server(options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.parse_options(args)
|
|
41
|
+
options = { port: 7827, host: "0.0.0.0", env: "development", reload: true }
|
|
42
|
+
|
|
43
|
+
i = 0
|
|
44
|
+
while i < args.length
|
|
45
|
+
case args[i]
|
|
46
|
+
when "-p", "--port"
|
|
47
|
+
options[:port] = args[i + 1].to_i
|
|
48
|
+
i += 2
|
|
49
|
+
when "-b", "--binding"
|
|
50
|
+
options[:host] = args[i + 1]
|
|
51
|
+
i += 2
|
|
52
|
+
when "-e", "--env"
|
|
53
|
+
options[:env] = args[i + 1]
|
|
54
|
+
i += 2
|
|
55
|
+
when "--no-reload"
|
|
56
|
+
options[:reload] = false
|
|
57
|
+
i += 1
|
|
58
|
+
else
|
|
59
|
+
i += 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
options
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.start_server(options)
|
|
67
|
+
ENV["TARSIER_ENV"] = options[:env]
|
|
68
|
+
|
|
69
|
+
puts "Tarsier Development Server"
|
|
70
|
+
puts "=" * 40
|
|
71
|
+
puts "Environment: #{options[:env]}"
|
|
72
|
+
puts "Listening on: http://#{options[:host]}:#{options[:port]}"
|
|
73
|
+
puts "Press Ctrl+C to stop"
|
|
74
|
+
puts "=" * 40
|
|
75
|
+
puts
|
|
76
|
+
|
|
77
|
+
# Try to use Puma if available, otherwise fall back to WEBrick
|
|
78
|
+
if puma_available?
|
|
79
|
+
start_puma(options)
|
|
80
|
+
elsif webrick_available?
|
|
81
|
+
start_webrick(options)
|
|
82
|
+
else
|
|
83
|
+
start_builtin(options)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.puma_available?
|
|
88
|
+
require "puma"
|
|
89
|
+
true
|
|
90
|
+
rescue LoadError
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.webrick_available?
|
|
95
|
+
require "webrick"
|
|
96
|
+
true
|
|
97
|
+
rescue LoadError
|
|
98
|
+
false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.start_puma(options)
|
|
102
|
+
require "puma/cli"
|
|
103
|
+
# Use only -b for binding, not both -p and -b
|
|
104
|
+
puma_args = ["-b", "tcp://#{options[:host]}:#{options[:port]}"]
|
|
105
|
+
Puma::CLI.new(puma_args).run
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.start_webrick(options)
|
|
109
|
+
require "rack"
|
|
110
|
+
require "rack/handler/webrick"
|
|
111
|
+
app = Rack::Builder.parse_file("config.ru")
|
|
112
|
+
Rack::Handler::WEBrick.run(app, Host: options[:host], Port: options[:port])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.start_builtin(options)
|
|
116
|
+
puts "Note: Install 'puma' gem for better performance"
|
|
117
|
+
require_relative "../server"
|
|
118
|
+
Tarsier::BuiltinServer.run(options)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|