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
data/exe/tarsier
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
# Main application class - the entry point for Tarsier apps
|
|
5
|
+
#
|
|
6
|
+
# Tarsier::Application is Rack-compatible and provides both a minimal
|
|
7
|
+
# Flask-style API and a more structured Rails-style API.
|
|
8
|
+
#
|
|
9
|
+
# @example Minimal (Flask-style)
|
|
10
|
+
# app = Tarsier.app do
|
|
11
|
+
# get('/') { { message: 'Hello!' } }
|
|
12
|
+
# post('/users') { |req| User.create(req.params) }
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @example Structured (Rails-style)
|
|
16
|
+
# Tarsier.application do
|
|
17
|
+
# configure { self.secret_key = ENV['SECRET'] }
|
|
18
|
+
# use Tarsier::Middleware::Logger
|
|
19
|
+
# routes do
|
|
20
|
+
# resources :users
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# @since 0.1.0
|
|
25
|
+
class Application
|
|
26
|
+
# @return [Router] the application router
|
|
27
|
+
attr_reader :router
|
|
28
|
+
|
|
29
|
+
# @return [Middleware::Stack] the middleware stack
|
|
30
|
+
attr_reader :middleware_stack
|
|
31
|
+
|
|
32
|
+
# @return [Configuration] application configuration
|
|
33
|
+
attr_reader :config
|
|
34
|
+
|
|
35
|
+
# Create a new application
|
|
36
|
+
def initialize
|
|
37
|
+
@router = Router.new
|
|
38
|
+
@middleware_stack = Middleware::Stack.new
|
|
39
|
+
@config = Configuration.new
|
|
40
|
+
@booted = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Configure the application
|
|
44
|
+
#
|
|
45
|
+
# @yield [Configuration] configuration object
|
|
46
|
+
# @return [self]
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# configure do
|
|
50
|
+
# self.secret_key = 'my-secret'
|
|
51
|
+
# self.log_level = :debug
|
|
52
|
+
# end
|
|
53
|
+
def configure(&block)
|
|
54
|
+
@config.instance_eval(&block) if block_given?
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Define routes using the router DSL
|
|
59
|
+
#
|
|
60
|
+
# @yield [Router] router instance
|
|
61
|
+
# @return [self]
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# routes do
|
|
65
|
+
# root to: 'home#index'
|
|
66
|
+
# resources :users
|
|
67
|
+
# end
|
|
68
|
+
def routes(&block)
|
|
69
|
+
@router.draw(&block)
|
|
70
|
+
self
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Add middleware to the stack
|
|
74
|
+
#
|
|
75
|
+
# @param middleware [Class] middleware class
|
|
76
|
+
# @param args [Array] middleware arguments
|
|
77
|
+
# @param options [Hash] middleware options
|
|
78
|
+
# @return [self]
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# use Tarsier::Middleware::Logger
|
|
82
|
+
# use Tarsier::Middleware::CORS, origins: ['*']
|
|
83
|
+
def use(middleware, *args, **options)
|
|
84
|
+
@middleware_stack.use(middleware, *args, **options)
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Define a GET route (Flask-style)
|
|
89
|
+
#
|
|
90
|
+
# @param path [String] route path
|
|
91
|
+
# @param to [String, Proc, nil] route handler
|
|
92
|
+
# @yield request handler block
|
|
93
|
+
# @return [self]
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# get('/') { { status: 'ok' } }
|
|
97
|
+
# get('/users/:id') { |req| User.find(req.params[:id]) }
|
|
98
|
+
def get(path, to: nil, **options, &block)
|
|
99
|
+
add_inline_route(:get, path, to, **options, &block)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Define a POST route (Flask-style)
|
|
103
|
+
#
|
|
104
|
+
# @param path [String] route path
|
|
105
|
+
# @param to [String, Proc, nil] route handler
|
|
106
|
+
# @yield request handler block
|
|
107
|
+
# @return [self]
|
|
108
|
+
def post(path, to: nil, **options, &block)
|
|
109
|
+
add_inline_route(:post, path, to, **options, &block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Define a PUT route (Flask-style)
|
|
113
|
+
#
|
|
114
|
+
# @param path [String] route path
|
|
115
|
+
# @param to [String, Proc, nil] route handler
|
|
116
|
+
# @yield request handler block
|
|
117
|
+
# @return [self]
|
|
118
|
+
def put(path, to: nil, **options, &block)
|
|
119
|
+
add_inline_route(:put, path, to, **options, &block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Define a PATCH route (Flask-style)
|
|
123
|
+
#
|
|
124
|
+
# @param path [String] route path
|
|
125
|
+
# @param to [String, Proc, nil] route handler
|
|
126
|
+
# @yield request handler block
|
|
127
|
+
# @return [self]
|
|
128
|
+
def patch(path, to: nil, **options, &block)
|
|
129
|
+
add_inline_route(:patch, path, to, **options, &block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Define a DELETE route (Flask-style)
|
|
133
|
+
#
|
|
134
|
+
# @param path [String] route path
|
|
135
|
+
# @param to [String, Proc, nil] route handler
|
|
136
|
+
# @yield request handler block
|
|
137
|
+
# @return [self]
|
|
138
|
+
def delete(path, to: nil, **options, &block)
|
|
139
|
+
add_inline_route(:delete, path, to, **options, &block)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Boot the application (compile routes, etc.)
|
|
143
|
+
#
|
|
144
|
+
# @return [self]
|
|
145
|
+
def boot!
|
|
146
|
+
return self if @booted
|
|
147
|
+
|
|
148
|
+
@router.compile!
|
|
149
|
+
@booted = true
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Check if application is booted
|
|
154
|
+
#
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def booted?
|
|
157
|
+
@booted
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Rack interface
|
|
161
|
+
#
|
|
162
|
+
# @param env [Hash] Rack environment
|
|
163
|
+
# @return [Array] Rack response tuple [status, headers, body]
|
|
164
|
+
def call(env)
|
|
165
|
+
boot! unless @booted
|
|
166
|
+
|
|
167
|
+
request = Request.new(env)
|
|
168
|
+
response = Response.new
|
|
169
|
+
|
|
170
|
+
begin
|
|
171
|
+
@middleware_stack.call(request, response, method(:dispatch))
|
|
172
|
+
rescue RouteNotFoundError => e
|
|
173
|
+
handle_not_found(request, response, e)
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
handle_error(request, response, e)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
response.finish
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Direct dispatch (bypasses Rack for performance)
|
|
182
|
+
#
|
|
183
|
+
# @param request [Request] request object
|
|
184
|
+
# @param response [Response] response object
|
|
185
|
+
# @return [Response]
|
|
186
|
+
def dispatch(request, response)
|
|
187
|
+
match = @router.match(request.method, request.path)
|
|
188
|
+
|
|
189
|
+
unless match
|
|
190
|
+
raise RouteNotFoundError.new(request.method, request.path)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
route, params = match
|
|
194
|
+
request = Request.new(request.env, params)
|
|
195
|
+
|
|
196
|
+
execute_route(route, request, response)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Get a named route path
|
|
200
|
+
#
|
|
201
|
+
# @param name [Symbol] route name
|
|
202
|
+
# @param params [Hash] route parameters
|
|
203
|
+
# @return [String]
|
|
204
|
+
def path_for(name, params = {})
|
|
205
|
+
@router.path_for(name, params)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Get all routes (for debugging/inspection)
|
|
209
|
+
#
|
|
210
|
+
# @return [Array<Route>]
|
|
211
|
+
def routes_list
|
|
212
|
+
@router.routes
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
def add_inline_route(method, path, to, **options, &block)
|
|
218
|
+
handler = block || to
|
|
219
|
+
@router.send(method, path, to: handler, **options)
|
|
220
|
+
self
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def execute_route(route, request, response)
|
|
224
|
+
if route.proc_handler?
|
|
225
|
+
execute_proc_handler(route, request, response)
|
|
226
|
+
else
|
|
227
|
+
execute_controller_action(route, request, response)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
response
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def execute_proc_handler(route, request, response)
|
|
234
|
+
handler = route.handler
|
|
235
|
+
result = case handler.arity
|
|
236
|
+
when 0 then handler.call
|
|
237
|
+
when 1 then handler.call(request)
|
|
238
|
+
else handler.call(request, response)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Auto-render hash as JSON
|
|
242
|
+
response.json(result) if result.is_a?(Hash) && !response.sent?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def execute_controller_action(route, request, response)
|
|
246
|
+
controller_class = resolve_controller_class(route.controller_class)
|
|
247
|
+
action = route.action_name
|
|
248
|
+
|
|
249
|
+
unless controller_class.method_defined?(action)
|
|
250
|
+
raise ActionNotFoundError.new(controller_class, action)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
controller = controller_class.new(request, response, action)
|
|
254
|
+
controller.dispatch
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def resolve_controller_class(class_name)
|
|
258
|
+
return class_name if class_name.is_a?(Class)
|
|
259
|
+
|
|
260
|
+
class_name.split("::").reduce(Object) do |mod, name|
|
|
261
|
+
mod.const_get(name)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def handle_not_found(request, response, error)
|
|
266
|
+
response.status = 404
|
|
267
|
+
response.content_type = "application/json"
|
|
268
|
+
response.body = JSON.generate({ error: "Not Found", path: error.path })
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def handle_error(request, response, error)
|
|
272
|
+
response.status = 500
|
|
273
|
+
response.content_type = "application/json"
|
|
274
|
+
|
|
275
|
+
body = { error: error.class.name, message: error.message }
|
|
276
|
+
body[:backtrace] = error.backtrace&.first(10) if Tarsier.development?
|
|
277
|
+
|
|
278
|
+
response.body = JSON.generate(body)
|
|
279
|
+
log_error(error) if Tarsier.production?
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def log_error(error)
|
|
283
|
+
warn "[Tarsier] #{error.class}: #{error.message}"
|
|
284
|
+
warn error.backtrace&.first(5)&.join("\n")
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Application configuration
|
|
289
|
+
#
|
|
290
|
+
# @since 0.1.0
|
|
291
|
+
class Configuration
|
|
292
|
+
# @return [String] secret key for sessions and CSRF
|
|
293
|
+
attr_accessor :secret_key
|
|
294
|
+
|
|
295
|
+
# @return [Symbol] session storage type
|
|
296
|
+
attr_accessor :session_store
|
|
297
|
+
|
|
298
|
+
# @return [Symbol] log level
|
|
299
|
+
attr_accessor :log_level
|
|
300
|
+
|
|
301
|
+
# @return [Boolean] serve static files
|
|
302
|
+
attr_accessor :static_files
|
|
303
|
+
|
|
304
|
+
# @return [Symbol] default response format
|
|
305
|
+
attr_accessor :default_format
|
|
306
|
+
|
|
307
|
+
# @return [Boolean] force SSL
|
|
308
|
+
attr_accessor :force_ssl
|
|
309
|
+
|
|
310
|
+
# @return [String] server host
|
|
311
|
+
attr_accessor :host
|
|
312
|
+
|
|
313
|
+
# @return [Integer] server port
|
|
314
|
+
attr_accessor :port
|
|
315
|
+
|
|
316
|
+
def initialize
|
|
317
|
+
@secret_key = ENV["TARSIER_SECRET_KEY"]
|
|
318
|
+
@session_store = :cookie
|
|
319
|
+
@log_level = :info
|
|
320
|
+
@static_files = true
|
|
321
|
+
@default_format = :json
|
|
322
|
+
@force_ssl = false
|
|
323
|
+
@host = "0.0.0.0"
|
|
324
|
+
@port = 7827
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Set configuration from hash
|
|
328
|
+
#
|
|
329
|
+
# @param options [Hash] configuration options
|
|
330
|
+
def set(options)
|
|
331
|
+
options.each do |key, value|
|
|
332
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class Console
|
|
7
|
+
HELP = <<~HELP
|
|
8
|
+
Usage: tarsier console [options]
|
|
9
|
+
|
|
10
|
+
Start an interactive console with your application loaded.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-e, --env ENV Environment to load (default: development)
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
tarsier console
|
|
17
|
+
tarsier console -e production
|
|
18
|
+
HELP
|
|
19
|
+
|
|
20
|
+
def self.show_help
|
|
21
|
+
puts HELP
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.run(args)
|
|
25
|
+
options = parse_options(args)
|
|
26
|
+
ENV["TARSIER_ENV"] = options[:env]
|
|
27
|
+
|
|
28
|
+
puts "Tarsier Console (#{options[:env]})"
|
|
29
|
+
puts "Type 'exit' to quit"
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
load_application
|
|
33
|
+
start_repl
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.parse_options(args)
|
|
37
|
+
options = { env: "development" }
|
|
38
|
+
|
|
39
|
+
i = 0
|
|
40
|
+
while i < args.length
|
|
41
|
+
case args[i]
|
|
42
|
+
when "-e", "--env"
|
|
43
|
+
options[:env] = args[i + 1]
|
|
44
|
+
i += 2
|
|
45
|
+
else
|
|
46
|
+
i += 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
options
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.load_application
|
|
54
|
+
if File.exist?("config/application.rb")
|
|
55
|
+
require "./config/application"
|
|
56
|
+
elsif File.exist?("app.rb")
|
|
57
|
+
require "./app"
|
|
58
|
+
end
|
|
59
|
+
rescue LoadError => e
|
|
60
|
+
puts "Warning: Could not load application: #{e.message}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.start_repl
|
|
64
|
+
require "irb"
|
|
65
|
+
IRB.start
|
|
66
|
+
rescue LoadError
|
|
67
|
+
simple_repl
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.simple_repl
|
|
71
|
+
loop do
|
|
72
|
+
print "tarsier> "
|
|
73
|
+
input = gets&.chomp
|
|
74
|
+
break if input.nil? || input == "exit"
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
result = eval(input) # rubocop:disable Security/Eval
|
|
78
|
+
puts "=> #{result.inspect}"
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
puts "Error: #{e.message}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class Generate
|
|
7
|
+
HELP = <<~HELP
|
|
8
|
+
Usage: tarsier generate <type> <name> [options]
|
|
9
|
+
|
|
10
|
+
Generate various components for your Tarsier application.
|
|
11
|
+
|
|
12
|
+
Types:
|
|
13
|
+
controller NAME [actions] Generate a controller with optional actions
|
|
14
|
+
model NAME [attributes] Generate a model with optional attributes
|
|
15
|
+
resource NAME [attrs] Generate model, controller, and routes
|
|
16
|
+
middleware NAME Generate a middleware class
|
|
17
|
+
migration NAME Generate a database migration
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
tarsier generate controller users index show create
|
|
21
|
+
tarsier generate model user name:string email:string
|
|
22
|
+
tarsier generate resource post title:string body:text
|
|
23
|
+
tarsier generate middleware authentication
|
|
24
|
+
HELP
|
|
25
|
+
|
|
26
|
+
def self.show_help
|
|
27
|
+
puts HELP
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.run(args)
|
|
31
|
+
type = args.shift
|
|
32
|
+
name = args.shift
|
|
33
|
+
|
|
34
|
+
if type.nil?
|
|
35
|
+
puts "Error: Please specify what to generate"
|
|
36
|
+
show_help
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if name.nil?
|
|
41
|
+
puts "Error: Please provide a name"
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
case type
|
|
46
|
+
when "controller", "c"
|
|
47
|
+
generate_controller(name, args)
|
|
48
|
+
when "model", "m"
|
|
49
|
+
generate_model(name, args)
|
|
50
|
+
when "resource", "r"
|
|
51
|
+
generate_resource(name, args)
|
|
52
|
+
when "middleware"
|
|
53
|
+
generate_middleware(name)
|
|
54
|
+
when "migration"
|
|
55
|
+
generate_migration(name, args)
|
|
56
|
+
else
|
|
57
|
+
puts "Unknown generator: #{type}"
|
|
58
|
+
show_help
|
|
59
|
+
exit 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.generate_controller(name, actions)
|
|
64
|
+
Generators::Controller.new(name, actions).generate
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.generate_model(name, attributes)
|
|
68
|
+
Generators::Model.new(name, attributes).generate
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.generate_resource(name, attributes)
|
|
72
|
+
Generators::Resource.new(name, attributes).generate
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.generate_middleware(name)
|
|
76
|
+
Generators::Middleware.new(name).generate
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.generate_migration(name, columns)
|
|
80
|
+
Generators::Migration.new(name, columns).generate
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class Help
|
|
7
|
+
BANNER = <<~BANNER
|
|
8
|
+
Tarsier - A modern, high-performance Ruby web framework
|
|
9
|
+
|
|
10
|
+
Usage: tarsier <command> [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
new APP_NAME Create a new Tarsier application
|
|
14
|
+
generate (g) Generate controllers, models, etc.
|
|
15
|
+
server (s) Start the development server
|
|
16
|
+
console (c) Start an interactive console
|
|
17
|
+
routes (r) Display all routes
|
|
18
|
+
version (v) Show Tarsier version
|
|
19
|
+
help Show this help message
|
|
20
|
+
|
|
21
|
+
Run 'tarsier <command> --help' for more information on a specific command.
|
|
22
|
+
BANNER
|
|
23
|
+
|
|
24
|
+
def self.run(args)
|
|
25
|
+
if args.first
|
|
26
|
+
show_command_help(args.first)
|
|
27
|
+
else
|
|
28
|
+
puts BANNER
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.show_command_help(command)
|
|
33
|
+
case command
|
|
34
|
+
when "new", "n"
|
|
35
|
+
New.show_help
|
|
36
|
+
when "generate", "g"
|
|
37
|
+
Generate.show_help
|
|
38
|
+
when "server", "s"
|
|
39
|
+
Server.show_help
|
|
40
|
+
when "routes", "r"
|
|
41
|
+
Routes.show_help
|
|
42
|
+
else
|
|
43
|
+
puts "Unknown command: #{command}"
|
|
44
|
+
puts BANNER
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarsier
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
class New
|
|
7
|
+
HELP = <<~HELP
|
|
8
|
+
Usage: tarsier new APP_NAME [options]
|
|
9
|
+
|
|
10
|
+
Create a new Tarsier application with the given name.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--api Create an API-only application (no views)
|
|
14
|
+
--minimal Create a minimal application
|
|
15
|
+
--skip-git Skip git initialization
|
|
16
|
+
--skip-bundle Skip bundle install
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
tarsier new my_app
|
|
20
|
+
tarsier new my_api --api
|
|
21
|
+
tarsier new my_app --minimal
|
|
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
|
+
app_name = args.first
|
|
31
|
+
|
|
32
|
+
if app_name.nil? || app_name.empty?
|
|
33
|
+
puts "Error: Please provide an application name"
|
|
34
|
+
puts "Usage: tarsier new APP_NAME"
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Generator.new(app_name, options).generate
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.parse_options(args)
|
|
42
|
+
options = { api: false, minimal: false, skip_git: false, skip_bundle: false }
|
|
43
|
+
|
|
44
|
+
args.delete_if do |arg|
|
|
45
|
+
case arg
|
|
46
|
+
when "--api" then options[:api] = true
|
|
47
|
+
when "--minimal" then options[:minimal] = true
|
|
48
|
+
when "--skip-git" then options[:skip_git] = true
|
|
49
|
+
when "--skip-bundle" then options[:skip_bundle] = true
|
|
50
|
+
else false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
options
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|