stipa 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 +31 -0
- data/LICENSE +21 -0
- data/README.md +305 -0
- data/bin/stipa +7 -0
- data/lib/js/stipa-vue.js +108 -0
- data/lib/stipa/app.rb +123 -0
- data/lib/stipa/cli.rb +39 -0
- data/lib/stipa/connection.rb +200 -0
- data/lib/stipa/generator.rb +19 -0
- data/lib/stipa/generators/api.rb +126 -0
- data/lib/stipa/generators/base.rb +117 -0
- data/lib/stipa/generators/vue.rb +442 -0
- data/lib/stipa/logger.rb +65 -0
- data/lib/stipa/middleware.rb +127 -0
- data/lib/stipa/request.rb +163 -0
- data/lib/stipa/response.rb +148 -0
- data/lib/stipa/server.rb +190 -0
- data/lib/stipa/static.rb +92 -0
- data/lib/stipa/template.rb +234 -0
- data/lib/stipa/thread_pool.rb +98 -0
- data/lib/stipa/version.rb +3 -0
- data/lib/stipa.rb +30 -0
- data/media/favicon.ico +0 -0
- data/media/logo.png +0 -0
- metadata +149 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Stipa
|
|
5
|
+
# ERB template engine with layout support and Vue.js integration helpers.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# engine = Stipa::Template.new(views_dir: 'views')
|
|
9
|
+
# html = engine.render('home', locals: { user: 'Alice' })
|
|
10
|
+
#
|
|
11
|
+
# With an explicit layout:
|
|
12
|
+
# html = engine.render('home', layout: 'layouts/admin')
|
|
13
|
+
#
|
|
14
|
+
# Without a layout (useful for partials / API fragments):
|
|
15
|
+
# html = engine.render('_row', locals: { item: obj }, layout: false)
|
|
16
|
+
#
|
|
17
|
+
# Directory conventions:
|
|
18
|
+
# views/
|
|
19
|
+
# layouts/
|
|
20
|
+
# application.html.erb ← default layout
|
|
21
|
+
# home.html.erb
|
|
22
|
+
# users/
|
|
23
|
+
# show.html.erb
|
|
24
|
+
# _sidebar.html.erb ← partials start with _
|
|
25
|
+
class Template
|
|
26
|
+
attr_reader :views_dir
|
|
27
|
+
|
|
28
|
+
def initialize(views_dir:)
|
|
29
|
+
@views_dir = File.expand_path(views_dir)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Render a template, optionally wrapped in a layout.
|
|
33
|
+
#
|
|
34
|
+
# template - name like 'home', 'users/show', or 'home.html.erb'
|
|
35
|
+
# locals - Hash of variables made available inside the template
|
|
36
|
+
# layout - :default → auto-detect views/layouts/application.html.erb
|
|
37
|
+
# a String → explicit layout name (same resolution as template)
|
|
38
|
+
# false → no layout
|
|
39
|
+
def render(template, locals: {}, layout: :default)
|
|
40
|
+
ctx = ViewContext.new(self)
|
|
41
|
+
locals.each { |k, v| ctx._set_local(k, v) }
|
|
42
|
+
|
|
43
|
+
content = render_file(resolve(template), ctx)
|
|
44
|
+
|
|
45
|
+
layout_path = resolve_layout(layout)
|
|
46
|
+
if layout_path && File.exist?(layout_path)
|
|
47
|
+
ctx._set_content(content)
|
|
48
|
+
render_file(layout_path, ctx)
|
|
49
|
+
else
|
|
50
|
+
content
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def render_file(path, ctx)
|
|
57
|
+
template = ERB.new(File.read(path), trim_mode: '-')
|
|
58
|
+
template.result(ctx._binding)
|
|
59
|
+
rescue Errno::ENOENT => e
|
|
60
|
+
raise "Template not found: #{path}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def resolve(name)
|
|
64
|
+
# If it already ends with .erb, use it as-is; otherwise add .html.erb
|
|
65
|
+
name = "#{name}.html.erb" unless name.end_with?('.erb')
|
|
66
|
+
File.join(@views_dir, name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def resolve_layout(layout)
|
|
70
|
+
return nil if layout == false
|
|
71
|
+
name = layout == :default ? 'layouts/application.html.erb' : "#{layout}.html.erb"
|
|
72
|
+
File.join(@views_dir, name)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# -------------------------------------------------------------------------
|
|
77
|
+
# View Context: the binding for ERB templates
|
|
78
|
+
# -------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
# The context object that becomes `self` inside every template.
|
|
81
|
+
# Includes helpers for rendering, HTML escaping, and Vue integration.
|
|
82
|
+
class ViewContext
|
|
83
|
+
def initialize(engine)
|
|
84
|
+
@engine = engine
|
|
85
|
+
@_blocks = {}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
# General helpers
|
|
90
|
+
# -------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
# Render a template (same engine, layout: false by default).
|
|
93
|
+
# Useful for partials; pass layout: :default if you want to wrap it.
|
|
94
|
+
def render_template(name, locals: {})
|
|
95
|
+
@engine.render(name, locals: locals, layout: false)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# HTML-escape a value. Use this in attributes or when interpolating
|
|
99
|
+
# untrusted user input inside text content.
|
|
100
|
+
#
|
|
101
|
+
# <div class="<%= escape_html(user.css_class) %>">
|
|
102
|
+
#
|
|
103
|
+
# Or use the alias:
|
|
104
|
+
#
|
|
105
|
+
# <div class="<%= h(user.css_class) %>">
|
|
106
|
+
def h(value)
|
|
107
|
+
ERB::Util.html_escape(value.to_s)
|
|
108
|
+
end
|
|
109
|
+
alias escape_html h
|
|
110
|
+
|
|
111
|
+
# -------------------------------------------------------------------------
|
|
112
|
+
# Vue.js helpers
|
|
113
|
+
# -------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
# Emit a Vue 3 component mount point.
|
|
116
|
+
#
|
|
117
|
+
# The Stīpa Vue bootstrapper (stipa-vue.js) picks up all elements with
|
|
118
|
+
# data-vue-component and mounts the corresponding registered component,
|
|
119
|
+
# passing data-props as the component's initial props.
|
|
120
|
+
#
|
|
121
|
+
# ERB:
|
|
122
|
+
# <%= vue_component("Counter", props: { initial: 5 }) %>
|
|
123
|
+
# <%= vue_component("SearchBox", props: { q: params[:q] }, class: "mt-4") %>
|
|
124
|
+
#
|
|
125
|
+
# Rendered HTML:
|
|
126
|
+
# <div data-vue-component="Counter" data-props="{"initial":5}"></div>
|
|
127
|
+
#
|
|
128
|
+
# Options:
|
|
129
|
+
# props: Hash passed as JSON to the component (default {})
|
|
130
|
+
# tag: HTML wrapper element (default 'div')
|
|
131
|
+
# Any other keyword args become HTML attributes on the wrapper element.
|
|
132
|
+
def vue_component(name, props: {}, tag: 'div', **html_attrs)
|
|
133
|
+
attr_parts = html_attrs.map { |k, v| %(#{k}="#{h(v)}") }
|
|
134
|
+
attr_str = attr_parts.empty? ? '' : " #{attr_parts.join(' ')}"
|
|
135
|
+
props_json = h(props.to_json)
|
|
136
|
+
%(<#{tag} data-vue-component="#{h(name)}" data-props="#{props_json}"#{attr_str}></#{tag}>)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Include Vue 3 from a CDN (or a local path you serve).
|
|
140
|
+
#
|
|
141
|
+
# <%= vue_script %> → unpkg, production build
|
|
142
|
+
# <%= vue_script(version: '3.4.21') %> → pin a specific version
|
|
143
|
+
# <%= vue_script(dev: true) %> → development build (warnings)
|
|
144
|
+
# <%= vue_script(src: '/vendor/vue.js') %> → self-hosted
|
|
145
|
+
def vue_script(src: nil, version: '3', dev: false)
|
|
146
|
+
unless src
|
|
147
|
+
build = dev ? 'vue.global.js' : 'vue.global.prod.js'
|
|
148
|
+
src = "https://unpkg.com/vue@#{version}/dist/#{build}"
|
|
149
|
+
end
|
|
150
|
+
%(<script src="#{src}"></script>)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Include the Stīpa Vue bootstrapper.
|
|
154
|
+
# This script auto-discovers vue_component mount points and mounts them.
|
|
155
|
+
# Must appear AFTER vue_script and AFTER any component registrations.
|
|
156
|
+
#
|
|
157
|
+
# <%= stipa_vue_bootstrap %>
|
|
158
|
+
def stipa_vue_bootstrap(src: '/stipa-vue.js')
|
|
159
|
+
%(<script src="#{src}"></script>)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Include one or more JavaScript files.
|
|
163
|
+
# <%= javascript_tag '/app.js' %>
|
|
164
|
+
# <%= javascript_tag '/a.js', '/b.js', type: 'module' %>
|
|
165
|
+
def javascript_tag(*srcs, type: nil, **attrs)
|
|
166
|
+
extra = attrs.map { |k, v| %( #{k}="#{h(v)}") }.join
|
|
167
|
+
type_attr = type ? %( type="#{h(type)}") : ''
|
|
168
|
+
srcs.map { |src| %(<script src="#{h(src)}"#{type_attr}#{extra}></script>) }.join("\n")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Include one or more stylesheets.
|
|
172
|
+
# <%= stylesheet_tag '/app.css' %>
|
|
173
|
+
# <%= stylesheet_tag '/reset.css', '/app.css' %>
|
|
174
|
+
def stylesheet_tag(*hrefs, **attrs)
|
|
175
|
+
extra = attrs.map { |k, v| %( #{k}="#{h(v)}") }.join
|
|
176
|
+
hrefs.map { |href| %(<link rel="stylesheet" href="#{h(href)}"#{extra}>) }.join("\n")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# -------------------------------------------------------------------------
|
|
180
|
+
# Private: template/layout machinery
|
|
181
|
+
# -------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
# Internal: inject a local variable as a method on this context.
|
|
184
|
+
def _set_local(name, value)
|
|
185
|
+
define_singleton_method(name) { value }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Internal: store the inner-page HTML for the layout's `yield`.
|
|
189
|
+
def _set_content(html)
|
|
190
|
+
@_layout_content = html
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Internal: expose binding for ERB.
|
|
194
|
+
def _binding
|
|
195
|
+
binding
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Called inside a layout template to render the inner page content.
|
|
199
|
+
#
|
|
200
|
+
# Use one of these in your layout:
|
|
201
|
+
# <body><%= content %></body>
|
|
202
|
+
# <body><%= yield_content %></body>
|
|
203
|
+
#
|
|
204
|
+
# (Plain ERB `yield` is a Ruby keyword and cannot be used here.)
|
|
205
|
+
def content
|
|
206
|
+
@_layout_content
|
|
207
|
+
end
|
|
208
|
+
alias yield_content content
|
|
209
|
+
|
|
210
|
+
# Named content blocks — store content from a page, render in the layout.
|
|
211
|
+
#
|
|
212
|
+
# Page: <% content_for :title do %>Home<% end %>
|
|
213
|
+
# Layout: <title><%= content_for(:title) %></title>
|
|
214
|
+
def content_for(section = nil, &block)
|
|
215
|
+
return @_blocks[section] if section && !block
|
|
216
|
+
@_blocks[section] = block.call if section && block
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Render a partial from within a template.
|
|
220
|
+
# Partials follow the Rails convention of a leading underscore on disk,
|
|
221
|
+
# but you refer to them without it.
|
|
222
|
+
#
|
|
223
|
+
# <%= render 'sidebar' %> → views/_sidebar.html.erb
|
|
224
|
+
# <%= render 'users/row', locals: { u: user } %> → views/users/_row.html.erb
|
|
225
|
+
def render(partial, locals: {})
|
|
226
|
+
name = if partial.include?('/')
|
|
227
|
+
partial.sub(%r{([^/]+)\z}, '_\1')
|
|
228
|
+
else
|
|
229
|
+
"_#{partial}"
|
|
230
|
+
end
|
|
231
|
+
@engine.render(name, locals: locals, layout: false)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module Stipa
|
|
4
|
+
# Bounded thread pool with a fixed-depth work queue.
|
|
5
|
+
#
|
|
6
|
+
# Design:
|
|
7
|
+
# - SizedQueue (stdlib) handles all mutex/condvar complexity.
|
|
8
|
+
# - Workers loop forever, pulling callables off the queue.
|
|
9
|
+
# - Shutdown sends one :stop sentinel per worker (poison-pill pattern)
|
|
10
|
+
# so every thread unblocks from its blocking `pop` and exits cleanly.
|
|
11
|
+
# - submit returns true/false (never blocks the caller by default).
|
|
12
|
+
#
|
|
13
|
+
# Capacity math:
|
|
14
|
+
# pool_size=32, queue_depth=64, avg handler=10ms
|
|
15
|
+
# → steady-state concurrency at 1k req/s = 10 threads (31% utilization)
|
|
16
|
+
# → 64-slot queue absorbs ~64ms burst before 503s begin
|
|
17
|
+
class ThreadPool
|
|
18
|
+
def initialize(size: 16, queue_depth: nil, on_error: nil)
|
|
19
|
+
@size = size
|
|
20
|
+
@queue_depth = queue_depth || size * 4
|
|
21
|
+
@on_error = on_error || method(:default_error_handler)
|
|
22
|
+
@queue = SizedQueue.new(@queue_depth)
|
|
23
|
+
@workers = []
|
|
24
|
+
@shutdown = false
|
|
25
|
+
@mutex = Mutex.new
|
|
26
|
+
@size.times { @workers << spawn_worker }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Submit a job (callable block) to the pool.
|
|
30
|
+
#
|
|
31
|
+
# mode: :drop — return false immediately if queue is full (default).
|
|
32
|
+
# :block — spin up to push_timeout seconds before giving up.
|
|
33
|
+
#
|
|
34
|
+
# Returns true if the job was accepted, false if dropped.
|
|
35
|
+
def submit(mode: :drop, push_timeout: 0.5, &job)
|
|
36
|
+
raise ArgumentError, 'job block required' unless job
|
|
37
|
+
return false if @shutdown
|
|
38
|
+
|
|
39
|
+
case mode
|
|
40
|
+
when :drop
|
|
41
|
+
# Non-blocking push. SizedQueue raises ThreadError when full.
|
|
42
|
+
begin
|
|
43
|
+
@queue.push(job, true) # true = non_block
|
|
44
|
+
true
|
|
45
|
+
rescue ThreadError
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
when :block
|
|
49
|
+
deadline = Time.now + push_timeout
|
|
50
|
+
loop do
|
|
51
|
+
begin
|
|
52
|
+
@queue.push(job, true)
|
|
53
|
+
return true
|
|
54
|
+
rescue ThreadError
|
|
55
|
+
return false if Time.now >= deadline
|
|
56
|
+
sleep 0.001 # 1ms spin — releases GVL while waiting
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "unknown mode: #{mode.inspect}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Graceful shutdown: stop accepting new jobs, drain the queue,
|
|
65
|
+
# then send a stop sentinel to every worker.
|
|
66
|
+
def shutdown(drain_timeout: 30.0)
|
|
67
|
+
@mutex.synchronize { @shutdown = true }
|
|
68
|
+
deadline = Time.now + drain_timeout
|
|
69
|
+
sleep 0.05 until @queue.empty? || Time.now >= deadline
|
|
70
|
+
# Poison-pill: one :stop per worker so each thread unblocks from pop
|
|
71
|
+
@size.times { @queue.push(:stop) }
|
|
72
|
+
@workers.each { |t| t.join(5) } # 5s hard join timeout per thread
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def queue_depth; @queue.length; end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def spawn_worker
|
|
80
|
+
Thread.new do
|
|
81
|
+
loop do
|
|
82
|
+
job = @queue.pop # blocks until work is available or :stop arrives
|
|
83
|
+
break if job == :stop
|
|
84
|
+
begin
|
|
85
|
+
job.call
|
|
86
|
+
rescue => e
|
|
87
|
+
@on_error.call(e, job)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def default_error_handler(err, _job)
|
|
94
|
+
warn "[Stipa::ThreadPool] worker error: #{err.class}: #{err.message}"
|
|
95
|
+
err.backtrace&.first(3)&.each { |l| warn " #{l}" }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/stipa.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# lib/stipa.rb — single require entry point
|
|
2
|
+
#
|
|
3
|
+
# Load order follows the dependency graph: leaf modules first,
|
|
4
|
+
# composite modules last. Adding `require 'stipa'` to a user's file
|
|
5
|
+
# loads the entire framework.
|
|
6
|
+
#
|
|
7
|
+
# Dependency order:
|
|
8
|
+
# version — no deps
|
|
9
|
+
# logger — no deps
|
|
10
|
+
# thread_pool — no deps
|
|
11
|
+
# middleware — no deps
|
|
12
|
+
# static — depends on middleware
|
|
13
|
+
# template — no deps (stdlib only: erb, json)
|
|
14
|
+
# request — no deps
|
|
15
|
+
# response — depends on template (via render helper)
|
|
16
|
+
# connection — depends on request, response
|
|
17
|
+
# server — depends on thread_pool, connection
|
|
18
|
+
# app — depends on server, middleware, template, request, response
|
|
19
|
+
|
|
20
|
+
require_relative 'stipa/version'
|
|
21
|
+
require_relative 'stipa/logger'
|
|
22
|
+
require_relative 'stipa/thread_pool'
|
|
23
|
+
require_relative 'stipa/middleware'
|
|
24
|
+
require_relative 'stipa/static'
|
|
25
|
+
require_relative 'stipa/template'
|
|
26
|
+
require_relative 'stipa/request'
|
|
27
|
+
require_relative 'stipa/response'
|
|
28
|
+
require_relative 'stipa/connection'
|
|
29
|
+
require_relative 'stipa/server'
|
|
30
|
+
require_relative 'stipa/app'
|
data/media/favicon.ico
ADDED
|
Binary file
|
data/media/logo.png
ADDED
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stipa
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pedro Harbs
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.20'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.20'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rubocop
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.50'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.50'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: yard
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.9'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.9'
|
|
69
|
+
description: |
|
|
70
|
+
Stīpa is a lightweight, zero-dependency HTTP/1.1 framework built entirely on Ruby stdlib.
|
|
71
|
+
|
|
72
|
+
Features:
|
|
73
|
+
- Pure stdlib (socket, thread, erb, json, securerandom)
|
|
74
|
+
- HTTP/1.1 with keep-alive, SO_REUSEPORT, and TCP_NODELAY
|
|
75
|
+
- Thread pool with bounded queue and graceful shutdown
|
|
76
|
+
- Pre-compiled middleware stack with zero per-request overhead
|
|
77
|
+
- ERB templates with layouts, partials, and Vue 3 island helpers
|
|
78
|
+
- CLI generator for MVC and API-only applications
|
|
79
|
+
- Structured logging in logfmt format
|
|
80
|
+
- Named route parameters via regex captures
|
|
81
|
+
email:
|
|
82
|
+
- harbspj@gmail.com
|
|
83
|
+
executables:
|
|
84
|
+
- stipa
|
|
85
|
+
extensions: []
|
|
86
|
+
extra_rdoc_files: []
|
|
87
|
+
files:
|
|
88
|
+
- CHANGELOG.md
|
|
89
|
+
- LICENSE
|
|
90
|
+
- README.md
|
|
91
|
+
- bin/stipa
|
|
92
|
+
- lib/js/stipa-vue.js
|
|
93
|
+
- lib/stipa.rb
|
|
94
|
+
- lib/stipa/app.rb
|
|
95
|
+
- lib/stipa/cli.rb
|
|
96
|
+
- lib/stipa/connection.rb
|
|
97
|
+
- lib/stipa/generator.rb
|
|
98
|
+
- lib/stipa/generators/api.rb
|
|
99
|
+
- lib/stipa/generators/base.rb
|
|
100
|
+
- lib/stipa/generators/vue.rb
|
|
101
|
+
- lib/stipa/logger.rb
|
|
102
|
+
- lib/stipa/middleware.rb
|
|
103
|
+
- lib/stipa/request.rb
|
|
104
|
+
- lib/stipa/response.rb
|
|
105
|
+
- lib/stipa/server.rb
|
|
106
|
+
- lib/stipa/static.rb
|
|
107
|
+
- lib/stipa/template.rb
|
|
108
|
+
- lib/stipa/thread_pool.rb
|
|
109
|
+
- lib/stipa/version.rb
|
|
110
|
+
- media/favicon.ico
|
|
111
|
+
- media/logo.png
|
|
112
|
+
homepage: https://github.com/pedroharbs/stipa
|
|
113
|
+
licenses:
|
|
114
|
+
- MIT
|
|
115
|
+
metadata:
|
|
116
|
+
bug_tracker_uri: https://github.com/pedroharbs/stipa/issues
|
|
117
|
+
changelog_uri: https://github.com/pedroharbs/stipa/releases
|
|
118
|
+
documentation_uri: https://github.com/pedroharbs/stipa
|
|
119
|
+
homepage_uri: https://github.com/pedroharbs/stipa
|
|
120
|
+
source_code_uri: https://github.com/pedroharbs/stipa
|
|
121
|
+
rubygems_mfa_required: 'true'
|
|
122
|
+
post_install_message: "╔══════════════════════════════════════════════════════════════════════════╗\n║
|
|
123
|
+
\ ║\n║ Welcome
|
|
124
|
+
to Stīpa! \U0001F680 ║\n║ ║\n║
|
|
125
|
+
\ Minimal, production-ready HTTP framework for Ruby with zero deps. ║\n║ ║\n║
|
|
126
|
+
\ Get started: ║\n║ $
|
|
127
|
+
stipa new my_app ║\n║ $ cd
|
|
128
|
+
my_app && bundle install && npm install ║\n║ $ bundle
|
|
129
|
+
exec ruby server.rb ║\n║ ║\n║
|
|
130
|
+
\ Documentation: https://github.com/pedroharbs/stipa ║\n║ ║\n╚══════════════════════════════════════════════════════════════════════════╝\n"
|
|
131
|
+
rdoc_options: []
|
|
132
|
+
require_paths:
|
|
133
|
+
- lib
|
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.1'
|
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - ">="
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0'
|
|
144
|
+
requirements: []
|
|
145
|
+
rubygems_version: 3.5.22
|
|
146
|
+
signing_key:
|
|
147
|
+
specification_version: 4
|
|
148
|
+
summary: Minimal, production-ready HTTP framework for Ruby
|
|
149
|
+
test_files: []
|