sitepress-server 5.0.0.beta1 → 5.0.0.beta2
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/lib/sitepress/process.rb +32 -0
- data/lib/sitepress/process_supervisor.rb +66 -0
- data/lib/sitepress/reloader.rb +159 -0
- data/lib/sitepress/server.rb +107 -56
- data/lib/sitepress/source_viewer.rb +85 -0
- data/lib/sitepress-server.rb +5 -3
- data/sitepress-server.gemspec +7 -5
- metadata +46 -24
- data/rails/app/assets/config/manifest.js +0 -0
- data/rails/app/controllers/application_controller.rb +0 -2
- data/rails/app/controllers/site_controller.rb +0 -62
- data/rails/app/helpers/application_helper.rb +0 -26
- data/rails/app/views/layouts/sitepress.html.erb +0 -20
- data/rails/app/views/site/action_template_error.html.erb +0 -74
- data/rails/app/views/site/not_found.html.erb +0 -17
- data/rails/app/views/site/parse_error.html.erb +0 -35
- data/rails/app/views/site/standard_error.html.erb +0 -29
- data/rails/public/_sitepress/images/logo.svg +0 -13
- data/rails/public/_sitepress/stylesheets/site.css +0 -84
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da4071703d528ea6fe2a3c53eb05014ad67bd91a32b119c67279e75b6a995804
|
|
4
|
+
data.tar.gz: de43f44a390779fdf934e336d9a6d975bf43c4f6486bd0ba9350662cd0b7d409
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88cecc2bcdd9566b56c0e1ef960580b41546e35f0afd7bd0be71a560277d03adb47f2527c55699a75c9d93821cf0fbc264e43d50b40645ccc7bab9cca28a0ca7
|
|
7
|
+
data.tar.gz: 30201abb12ead0ffc1e15f536cf2d5ca20fe4b7e566f41c7ecbe21776da2fa30094440931a5057adef8e2ee2a748102df996a021517179da2125fccfe3b685f4
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Sitepress
|
|
2
|
+
# Represents a labeled shell command that runs as part of the development server.
|
|
3
|
+
# Used for things like Tailwind CSS watchers, esbuild, etc.
|
|
4
|
+
class Process
|
|
5
|
+
COLORS = [:red, :green, :yellow, :blue, :magenta, :cyan].freeze
|
|
6
|
+
|
|
7
|
+
attr_reader :label, :command
|
|
8
|
+
attr_accessor :color
|
|
9
|
+
|
|
10
|
+
def initialize(label:, command:)
|
|
11
|
+
@label = label.to_sym
|
|
12
|
+
@command = command
|
|
13
|
+
@color = nil
|
|
14
|
+
@pid = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Run the process, yielding each line of output.
|
|
18
|
+
# This blocks until the process exits.
|
|
19
|
+
def run(&block)
|
|
20
|
+
IO.popen(command, err: [:child, :out]) do |io|
|
|
21
|
+
io.each_line do |line|
|
|
22
|
+
yield line.chomp if block_given?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Assign a color based on index (for supervisor to assign unique colors)
|
|
28
|
+
def self.color_for_index(index)
|
|
29
|
+
COLORS[index % COLORS.length]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "async"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
# Runs multiple processes concurrently, with colored and labeled output.
|
|
5
|
+
class ProcessSupervisor
|
|
6
|
+
ANSI_COLORS = {
|
|
7
|
+
red: "\e[31m",
|
|
8
|
+
green: "\e[32m",
|
|
9
|
+
yellow: "\e[33m",
|
|
10
|
+
blue: "\e[34m",
|
|
11
|
+
magenta: "\e[35m",
|
|
12
|
+
cyan: "\e[36m",
|
|
13
|
+
reset: "\e[0m"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :processes
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@processes = []
|
|
20
|
+
@mutex = Mutex.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add(process)
|
|
24
|
+
process.color = Process.color_for_index(@processes.size)
|
|
25
|
+
@processes << process
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Run all processes concurrently using Async.
|
|
29
|
+
# This method blocks until all processes exit.
|
|
30
|
+
def run
|
|
31
|
+
return if @processes.empty?
|
|
32
|
+
|
|
33
|
+
Sync do |task|
|
|
34
|
+
@processes.each do |process|
|
|
35
|
+
task.async do
|
|
36
|
+
run_process(process)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def run_process(process)
|
|
45
|
+
process.run do |line|
|
|
46
|
+
output(process, line)
|
|
47
|
+
end
|
|
48
|
+
rescue => e
|
|
49
|
+
output(process, "Error: #{e.message}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def output(process, line)
|
|
53
|
+
color = ANSI_COLORS[process.color] || ""
|
|
54
|
+
reset = ANSI_COLORS[:reset]
|
|
55
|
+
label = process.label.to_s.ljust(max_label_length)
|
|
56
|
+
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
puts "#{color}[#{label}]#{reset} #{line}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def max_label_length
|
|
63
|
+
@max_label_length ||= @processes.map { |p| p.label.to_s.length }.max || 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require "listen"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
# Manages browser reloading: SSE client connections and file watching.
|
|
5
|
+
class Reloader
|
|
6
|
+
attr_reader :watch_paths
|
|
7
|
+
attr_accessor :logger
|
|
8
|
+
|
|
9
|
+
def initialize(logger: nil)
|
|
10
|
+
@clients = {}
|
|
11
|
+
@next_client_id = 0
|
|
12
|
+
@watch_paths = []
|
|
13
|
+
@listener = nil
|
|
14
|
+
@logger = logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Add a path to watch for changes.
|
|
18
|
+
def watch(path)
|
|
19
|
+
@watch_paths << path.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Create an SSE connection for a client.
|
|
23
|
+
# Returns a Rack response tuple.
|
|
24
|
+
def connect
|
|
25
|
+
client_id = @next_client_id += 1
|
|
26
|
+
body = SSEConnection.new(client_id, @clients)
|
|
27
|
+
[200, sse_headers, body]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Notify all connected clients to reload.
|
|
31
|
+
def notify(modified: [], added: [], removed: [])
|
|
32
|
+
log "Files changed at #{Time.now}"
|
|
33
|
+
modified.each { |f| log " Modified #{f}" }
|
|
34
|
+
added.each { |f| log " Added #{f}" }
|
|
35
|
+
removed.each { |f| log " Removed #{f}" }
|
|
36
|
+
log "Reloading #{@clients.size} client(s)"
|
|
37
|
+
log ""
|
|
38
|
+
log ""
|
|
39
|
+
|
|
40
|
+
@clients.each_value do |queue|
|
|
41
|
+
queue << "event: change\ndata: \n\n" rescue nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Start watching files for changes.
|
|
46
|
+
# Call this within an Async task.
|
|
47
|
+
def start_watching
|
|
48
|
+
paths = @watch_paths.select { |p| File.directory?(p) }
|
|
49
|
+
return if paths.empty?
|
|
50
|
+
|
|
51
|
+
log "Reloader watching"
|
|
52
|
+
|
|
53
|
+
@listener = Listen.to(*paths) do |modified, added, removed|
|
|
54
|
+
notify(modified: modified, added: added, removed: removed)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@listener.start
|
|
58
|
+
sleep # Keep the task alive
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Stop watching files.
|
|
62
|
+
def stop_watching
|
|
63
|
+
@listener&.stop
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns a Rack middleware that injects the reload script.
|
|
67
|
+
def middleware
|
|
68
|
+
Middleware
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def log(message)
|
|
74
|
+
@logger&.puts message
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def sse_headers
|
|
78
|
+
{
|
|
79
|
+
"Content-Type" => "text/event-stream",
|
|
80
|
+
"Cache-Control" => "no-cache"
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# SSE response body that yields messages from a queue.
|
|
85
|
+
class SSEConnection
|
|
86
|
+
def initialize(client_id, clients)
|
|
87
|
+
@client_id = client_id
|
|
88
|
+
@clients = clients
|
|
89
|
+
@queue = Queue.new
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def each
|
|
93
|
+
@clients[@client_id] = @queue
|
|
94
|
+
yield "data: connected\n\n"
|
|
95
|
+
|
|
96
|
+
loop do
|
|
97
|
+
msg = @queue.pop
|
|
98
|
+
break if msg == :close
|
|
99
|
+
yield msg
|
|
100
|
+
end
|
|
101
|
+
rescue Errno::EPIPE, IOError
|
|
102
|
+
# Client disconnected
|
|
103
|
+
ensure
|
|
104
|
+
@clients.delete(@client_id)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def close
|
|
108
|
+
@queue << :close rescue nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Rack middleware that injects reload script into HTML responses.
|
|
113
|
+
class Middleware
|
|
114
|
+
SCRIPT = <<~HTML
|
|
115
|
+
<script>
|
|
116
|
+
(function() {
|
|
117
|
+
var es = new EventSource("/_sitepress/changes");
|
|
118
|
+
es.addEventListener("change", function() { location.reload(); });
|
|
119
|
+
})();
|
|
120
|
+
</script>
|
|
121
|
+
HTML
|
|
122
|
+
|
|
123
|
+
def initialize(app)
|
|
124
|
+
@app = app
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def call(env)
|
|
128
|
+
status, headers, body = @app.call(env)
|
|
129
|
+
|
|
130
|
+
if html_response?(headers)
|
|
131
|
+
body = inject_script(body)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
[status, headers, body]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def html_response?(headers)
|
|
140
|
+
content_type = headers["Content-Type"] || headers["content-type"] || ""
|
|
141
|
+
content_type.include?("text/html")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def inject_script(body)
|
|
145
|
+
html = +""
|
|
146
|
+
body.each { |chunk| html << chunk }
|
|
147
|
+
body.close if body.respond_to?(:close)
|
|
148
|
+
|
|
149
|
+
if html.include?("</body>")
|
|
150
|
+
html.sub!("</body>", "#{SCRIPT}</body>")
|
|
151
|
+
else
|
|
152
|
+
html << SCRIPT
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
[html]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/lib/sitepress/server.rb
CHANGED
|
@@ -1,60 +1,111 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "
|
|
3
|
-
require "
|
|
1
|
+
require "async"
|
|
2
|
+
require "async/http/endpoint"
|
|
3
|
+
require "console"
|
|
4
|
+
require "falcon"
|
|
5
|
+
require "protocol/rack/adapter"
|
|
4
6
|
|
|
5
|
-
# Require the gems listed in Gemfile, including any gems
|
|
6
|
-
# you've limited to :test, :development, or :production.
|
|
7
|
-
Bundler.require(*Rails.groups)
|
|
8
|
-
|
|
9
|
-
# Configure the rails application.
|
|
10
7
|
module Sitepress
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
8
|
+
# Development server using Falcon with process supervision and optional reloading.
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# app = MyRackApp.new
|
|
12
|
+
# reloader = Sitepress::Reloader.new
|
|
13
|
+
# reloader.watch "./pages"
|
|
14
|
+
# server = Sitepress::Server.new(app, reloader: reloader)
|
|
15
|
+
# server.add_process :css, "tailwindcss -w -i ./assets/site.css -o ./public/site.css"
|
|
16
|
+
# server.run
|
|
17
|
+
#
|
|
18
|
+
class Server
|
|
19
|
+
DEFAULT_HOST = "127.0.0.1".freeze
|
|
20
|
+
DEFAULT_PORT = 8080
|
|
21
|
+
|
|
22
|
+
attr_reader :app, :processes, :reloader
|
|
23
|
+
attr_accessor :host, :port
|
|
24
|
+
|
|
25
|
+
def initialize(app, reloader: nil)
|
|
26
|
+
@app = app
|
|
27
|
+
@processes = []
|
|
28
|
+
@reloader = reloader
|
|
29
|
+
@host = DEFAULT_HOST
|
|
30
|
+
@port = DEFAULT_PORT
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Add a labeled process to run alongside the server.
|
|
34
|
+
def add_process(label, command)
|
|
35
|
+
process = Process.new(label: label, command: command)
|
|
36
|
+
@processes << process
|
|
37
|
+
process
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Start the server and all configured processes.
|
|
41
|
+
def run
|
|
42
|
+
print_banner
|
|
43
|
+
|
|
44
|
+
Sync do |task|
|
|
45
|
+
start_processes(task)
|
|
46
|
+
start_reloader(task)
|
|
47
|
+
start_falcon
|
|
48
|
+
end
|
|
49
|
+
rescue Interrupt
|
|
50
|
+
puts "\nShutting down..."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def print_banner
|
|
56
|
+
# Suppress noisy EPIPE warnings when SSE clients disconnect during navigation
|
|
57
|
+
::Console.logger.level = :error if reloader
|
|
58
|
+
|
|
59
|
+
puts "Sitepress server starting..."
|
|
60
|
+
puts " URL: http://#{host}:#{port}/"
|
|
61
|
+
puts " Reloader: #{reloader ? 'enabled' : 'disabled'}"
|
|
62
|
+
puts ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def start_processes(task)
|
|
66
|
+
return if @processes.empty?
|
|
67
|
+
|
|
68
|
+
task.async do
|
|
69
|
+
supervisor = ProcessSupervisor.new
|
|
70
|
+
@processes.each { |p| supervisor.add(p) }
|
|
71
|
+
supervisor.run
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def start_reloader(task)
|
|
76
|
+
return unless reloader
|
|
77
|
+
|
|
78
|
+
task.async { reloader.start_watching }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def start_falcon
|
|
82
|
+
rack_app = build_rack_app
|
|
83
|
+
adapted_app = Protocol::Rack::Adapter.new(rack_app)
|
|
84
|
+
endpoint = Async::HTTP::Endpoint.parse("http://#{host}:#{port}/")
|
|
85
|
+
|
|
86
|
+
server = Falcon::Server.new(adapted_app, endpoint)
|
|
87
|
+
server.run.wait
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_rack_app
|
|
91
|
+
main_app = app
|
|
92
|
+
r = reloader
|
|
93
|
+
|
|
94
|
+
Rack::Builder.new do
|
|
95
|
+
if r
|
|
96
|
+
use r.middleware
|
|
97
|
+
|
|
98
|
+
map "/_sitepress/changes" do
|
|
99
|
+
run ->(env) { r.connect }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
map "/_sitepress/source" do
|
|
104
|
+
run SourceViewer.new
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
run main_app
|
|
108
|
+
end
|
|
109
|
+
end
|
|
59
110
|
end
|
|
60
111
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
class SourceViewer
|
|
5
|
+
CONTEXT_LINES = 5
|
|
6
|
+
|
|
7
|
+
def call(env)
|
|
8
|
+
return method_not_allowed unless env["REQUEST_METHOD"] == "GET"
|
|
9
|
+
|
|
10
|
+
params = Rack::Utils.parse_query(env["QUERY_STRING"])
|
|
11
|
+
file = params["file"]
|
|
12
|
+
line = params["line"].to_i
|
|
13
|
+
|
|
14
|
+
return bad_request("Missing file parameter") unless file
|
|
15
|
+
return bad_request("Missing line parameter") unless line > 0
|
|
16
|
+
return not_found("File not found") unless File.exist?(file)
|
|
17
|
+
return forbidden("Access denied") unless allowed_path?(file)
|
|
18
|
+
|
|
19
|
+
source = read_source(file, line)
|
|
20
|
+
json_response(source)
|
|
21
|
+
rescue => e
|
|
22
|
+
error_response(e.message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def read_source(file, target_line)
|
|
28
|
+
lines = File.readlines(file)
|
|
29
|
+
start_line = [target_line - CONTEXT_LINES, 1].max
|
|
30
|
+
end_line = [target_line + CONTEXT_LINES, lines.length].min
|
|
31
|
+
|
|
32
|
+
source_lines = (start_line..end_line).map do |num|
|
|
33
|
+
{
|
|
34
|
+
number: num,
|
|
35
|
+
code: lines[num - 1]&.chomp || "",
|
|
36
|
+
error: num == target_line
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
file: file,
|
|
42
|
+
line: target_line,
|
|
43
|
+
lines: source_lines
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def allowed_path?(file)
|
|
48
|
+
expanded = File.expand_path(file)
|
|
49
|
+
|
|
50
|
+
# Only allow .rb and .erb files (source code)
|
|
51
|
+
return false unless expanded.end_with?('.rb', '.erb', '.html.erb')
|
|
52
|
+
|
|
53
|
+
# Block access to sensitive paths
|
|
54
|
+
sensitive = %w[/etc /var /private /root]
|
|
55
|
+
return false if sensitive.any? { |s| expanded.start_with?(s) }
|
|
56
|
+
|
|
57
|
+
# Allow any readable source file in development
|
|
58
|
+
File.readable?(expanded)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def json_response(data)
|
|
62
|
+
[200, { "Content-Type" => "application/json" }, [JSON.generate(data)]]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bad_request(message)
|
|
66
|
+
[400, { "Content-Type" => "application/json" }, [JSON.generate({ error: message })]]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def not_found(message)
|
|
70
|
+
[404, { "Content-Type" => "application/json" }, [JSON.generate({ error: message })]]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def forbidden(message)
|
|
74
|
+
[403, { "Content-Type" => "application/json" }, [JSON.generate({ error: message })]]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def method_not_allowed
|
|
78
|
+
[405, { "Content-Type" => "application/json" }, [JSON.generate({ error: "Method not allowed" })]]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def error_response(message)
|
|
82
|
+
[500, { "Content-Type" => "application/json" }, [JSON.generate({ error: message })]]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/sitepress-server.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
require "sitepress-core"
|
|
2
|
-
|
|
3
1
|
module Sitepress
|
|
4
|
-
autoload :
|
|
2
|
+
autoload :Process, "sitepress/process"
|
|
3
|
+
autoload :ProcessSupervisor, "sitepress/process_supervisor"
|
|
4
|
+
autoload :Reloader, "sitepress/reloader"
|
|
5
|
+
autoload :Server, "sitepress/server"
|
|
6
|
+
autoload :SourceViewer, "sitepress/source_viewer"
|
|
5
7
|
end
|
data/sitepress-server.gemspec
CHANGED
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.authors = ["Brad Gessler"]
|
|
10
10
|
spec.email = ["bradgessler@gmail.com"]
|
|
11
11
|
spec.licenses = ["MIT"]
|
|
12
|
-
spec.summary = %q{
|
|
12
|
+
spec.summary = %q{Falcon-based development server for Sitepress with process supervision and live reload.}
|
|
13
13
|
spec.homepage = "https://sitepress.cc/"
|
|
14
14
|
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
@@ -17,11 +17,13 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.metadata["changelog_uri"] = "https://github.com/sitepress/sitepress/tags"
|
|
18
18
|
|
|
19
19
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
20
|
-
spec.bindir = "exe"
|
|
21
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
20
|
spec.require_paths = ["lib"]
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
# Falcon web server
|
|
23
|
+
spec.add_runtime_dependency "falcon", ">= 0.47"
|
|
24
|
+
spec.add_runtime_dependency "async", ">= 2.0"
|
|
25
|
+
spec.add_runtime_dependency "async-http", ">= 0.60"
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
# File watching for live reload
|
|
28
|
+
spec.add_runtime_dependency "listen", ">= 3.0"
|
|
27
29
|
end
|
metadata
CHANGED
|
@@ -1,42 +1,70 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sitepress-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.0.0.
|
|
4
|
+
version: 5.0.0.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brad Gessler
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-03-06 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: falcon
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :
|
|
18
|
+
version: '0.47'
|
|
19
|
+
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
25
|
+
version: '0.47'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: async
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- -
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: async-http
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
31
45
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
46
|
+
version: '0.60'
|
|
33
47
|
type: :runtime
|
|
34
48
|
prerelease: false
|
|
35
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
50
|
requirements:
|
|
37
|
-
- -
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.60'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: listen
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
38
66
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
67
|
+
version: '3.0'
|
|
40
68
|
email:
|
|
41
69
|
- bradgessler@gmail.com
|
|
42
70
|
executables: []
|
|
@@ -44,18 +72,11 @@ extensions: []
|
|
|
44
72
|
extra_rdoc_files: []
|
|
45
73
|
files:
|
|
46
74
|
- lib/sitepress-server.rb
|
|
75
|
+
- lib/sitepress/process.rb
|
|
76
|
+
- lib/sitepress/process_supervisor.rb
|
|
77
|
+
- lib/sitepress/reloader.rb
|
|
47
78
|
- lib/sitepress/server.rb
|
|
48
|
-
-
|
|
49
|
-
- rails/app/controllers/application_controller.rb
|
|
50
|
-
- rails/app/controllers/site_controller.rb
|
|
51
|
-
- rails/app/helpers/application_helper.rb
|
|
52
|
-
- rails/app/views/layouts/sitepress.html.erb
|
|
53
|
-
- rails/app/views/site/action_template_error.html.erb
|
|
54
|
-
- rails/app/views/site/not_found.html.erb
|
|
55
|
-
- rails/app/views/site/parse_error.html.erb
|
|
56
|
-
- rails/app/views/site/standard_error.html.erb
|
|
57
|
-
- rails/public/_sitepress/images/logo.svg
|
|
58
|
-
- rails/public/_sitepress/stylesheets/site.css
|
|
79
|
+
- lib/sitepress/source_viewer.rb
|
|
59
80
|
- sitepress-server.gemspec
|
|
60
81
|
homepage: https://sitepress.cc/
|
|
61
82
|
licenses:
|
|
@@ -80,5 +101,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
101
|
requirements: []
|
|
81
102
|
rubygems_version: 3.6.2
|
|
82
103
|
specification_version: 4
|
|
83
|
-
summary:
|
|
104
|
+
summary: Falcon-based development server for Sitepress with process supervision and
|
|
105
|
+
live reload.
|
|
84
106
|
test_files: []
|
|
File without changes
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
class SiteController < ApplicationController
|
|
2
|
-
include Sitepress::SitePages
|
|
3
|
-
|
|
4
|
-
DEFAULT_SITE_LAYOUT = "layouts/layout".freeze
|
|
5
|
-
|
|
6
|
-
# This `rescue_from` order is important; it must come before the
|
|
7
|
-
# `include Sitepress::SitePages` statement; otherwise exceptions
|
|
8
|
-
# won't be properly handled.
|
|
9
|
-
rescue_from Exception, with: :standard_error
|
|
10
|
-
rescue_from ActionView::Template::Error, with: :action_view_template_error
|
|
11
|
-
rescue_from Sitepress::ParseError, with: :parse_error
|
|
12
|
-
rescue_from Sitepress::ResourceNotFoundError, with: :page_not_found
|
|
13
|
-
|
|
14
|
-
layout :site_layout
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
def site_layout
|
|
18
|
-
DEFAULT_SITE_LAYOUT if template_exists? DEFAULT_SITE_LAYOUT
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def standard_error(exception)
|
|
22
|
-
render_exception(template: "standard_error", exception: exception)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def action_view_template_error(exception)
|
|
26
|
-
render_exception(template: "action_template_error", exception: exception)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def parse_error(exception)
|
|
30
|
-
raise exception unless has_error_reporting_enabled?
|
|
31
|
-
|
|
32
|
-
@title = "Parse error"
|
|
33
|
-
@exception = exception
|
|
34
|
-
render "parse_error", layout: "sitepress", status: :internal_server_error, formats: :html
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def render_exception(template:, exception:)
|
|
38
|
-
raise exception unless has_error_reporting_enabled?
|
|
39
|
-
|
|
40
|
-
@title = "Error in resource #{current_page.asset.path}"
|
|
41
|
-
@exception = exception
|
|
42
|
-
render template, layout: "sitepress", status: :internal_server_error, formats: :html
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def page_not_found(exception)
|
|
46
|
-
raise exception unless has_error_reporting_enabled?
|
|
47
|
-
not_found
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def not_found
|
|
51
|
-
@title = "Could not find resource at #{request.path}"
|
|
52
|
-
render "not_found", layout: "sitepress", status: :not_found
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def has_error_reporting_enabled?
|
|
56
|
-
Sitepress::Server.config.enable_site_error_reporting
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def reload_site?
|
|
60
|
-
Sitepress::Server.config.enable_site_reloading
|
|
61
|
-
end
|
|
62
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module ApplicationHelper
|
|
2
|
-
DEFAULT_TITLE_KEY = "title".freeze
|
|
3
|
-
|
|
4
|
-
DEFAULT_ORDER_KEY = "order".freeze
|
|
5
|
-
|
|
6
|
-
# Links to a Sitepress::Resource. If the link does not have a block, the `title`
|
|
7
|
-
# attribute from Resource#data is used to create the text link.
|
|
8
|
-
def link_to_page(page, *args, title_key: DEFAULT_TITLE_KEY, **kwargs, &block)
|
|
9
|
-
if block_given?
|
|
10
|
-
link_to page.request_path, *args, **kwargs, &block
|
|
11
|
-
else
|
|
12
|
-
link_to page.data[DEFAULT_TITLE_KEY], page.request_path, *args, **kwargs
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Render a block within a layout. This is a useful, and prefered way, to handle
|
|
17
|
-
# nesting layouts, within Sitepress
|
|
18
|
-
def render_layout(layout, locals = {}, &block)
|
|
19
|
-
render inline: capture(&block), layout: "layouts/#{layout}", locals: locals
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Orders pages via the
|
|
23
|
-
def order_pages(pages, order_key: DEFAULT_ORDER_KEY)
|
|
24
|
-
pages.sort_by { |r| r.data.fetch(order_key, Float::INFINITY) }
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset='utf-8'>
|
|
5
|
-
<title><%= @title || "Sitepress" %></title>
|
|
6
|
-
<link href='/_sitepress/stylesheets/site.css' rel='stylesheet' type='text/css'>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<header>
|
|
10
|
-
<img src='/_sitepress/images/logo.svg' id='logo'>
|
|
11
|
-
<h1 id="title"><%= @title || "Sitepress" %></h1>
|
|
12
|
-
</header>
|
|
13
|
-
<main>
|
|
14
|
-
<%= yield %>
|
|
15
|
-
</main>
|
|
16
|
-
</body>
|
|
17
|
-
<footer>
|
|
18
|
-
Running version Sitepress <%= Sitepress::VERSION %>. Visit <a href="https://sitepress.cc/">sitepress.cc</a> for documentation, help, and updates.
|
|
19
|
-
</footer>
|
|
20
|
-
</html>
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
<% body_offset = current_page.asset.respond_to?(:body_line_offset) ? current_page.asset.body_line_offset : 1 %>
|
|
2
|
-
<% adjusted_line = @exception.line_number.to_i + body_offset - 1 %>
|
|
3
|
-
<% plain_text_error = <<~ERROR
|
|
4
|
-
#{@exception.class}: #{@exception.message}
|
|
5
|
-
File: #{current_page.asset.path}:#{adjusted_line}
|
|
6
|
-
|
|
7
|
-
Source:
|
|
8
|
-
#{@exception.annotated_source_code.join("\n")}
|
|
9
|
-
|
|
10
|
-
Backtrace:
|
|
11
|
-
#{@exception.backtrace.first(15).join("\n")}
|
|
12
|
-
ERROR
|
|
13
|
-
%>
|
|
14
|
-
|
|
15
|
-
<button onclick="navigator.clipboard.writeText(document.getElementById('error-text').textContent).then(() => this.textContent = 'Copied!').catch(() => this.textContent = 'Failed')" style="float: right; padding: 8px 16px; cursor: pointer; background: transparent; border: 1px solid rgba(255,255,255,0.3); color: inherit; border-radius: 4px; font-size: 14px; transition: all 0.2s ease;" onmouseover="this.style.background='rgba(255,255,255,0.1)'; this.style.borderColor='rgba(255,255,255,0.5)'" onmouseout="this.style.background='transparent'; this.style.borderColor='rgba(255,255,255,0.3)'">Copy Error</button>
|
|
16
|
-
<pre id="error-text" style="display: none;"><%= plain_text_error %></pre>
|
|
17
|
-
|
|
18
|
-
<h2><%= @exception.class %>: <%= @exception.message %> in <%= current_page.asset.path %>:<%= adjusted_line %></h2>
|
|
19
|
-
<h3>Source</h3>
|
|
20
|
-
|
|
21
|
-
<code>
|
|
22
|
-
<pre><%= current_page.asset.path %>:<%= adjusted_line %>
|
|
23
|
-
<%= @exception.annotated_source_code.join("\n") %></pre>
|
|
24
|
-
</code>
|
|
25
|
-
|
|
26
|
-
<%
|
|
27
|
-
# Clean up ugly ERB-generated method names in stack traces
|
|
28
|
-
clean_trace = ->(line) {
|
|
29
|
-
if line =~ /\.erb:\d+:in '___/
|
|
30
|
-
line.sub(/:in '___.*'$/, '')
|
|
31
|
-
else
|
|
32
|
-
line
|
|
33
|
-
end
|
|
34
|
-
}
|
|
35
|
-
app_trace = @exception.backtrace.reject { |line| line.include?('/gems/') || line.include?('/ruby/') }.map(&clean_trace)
|
|
36
|
-
framework_trace = @exception.backtrace.select { |line| line.include?('/gems/') || line.include?('/ruby/') }.map(&clean_trace)
|
|
37
|
-
%>
|
|
38
|
-
|
|
39
|
-
<h3>Backtrace</h3>
|
|
40
|
-
<code>
|
|
41
|
-
<pre style="margin: 0;"><%= app_trace.first(10).join("\n") %></pre>
|
|
42
|
-
<% if framework_trace.any? %>
|
|
43
|
-
<details style="margin-top: 1em;">
|
|
44
|
-
<summary style="cursor: pointer; font-size: 0.85em; padding: 0.5em 0; border-top: 1px solid rgba(255,255,255,0.15); user-select: none; display: flex; align-items: center; gap: 0.5em; opacity: 0.7;">
|
|
45
|
-
<span style="display: inline-block; transition: transform 0.2s ease;">▶</span>
|
|
46
|
-
Framework Trace (<%= framework_trace.length %> lines)
|
|
47
|
-
</summary>
|
|
48
|
-
<pre style="margin: 0.5em 0 0 0;"><%= framework_trace.join("\n") %></pre>
|
|
49
|
-
</details>
|
|
50
|
-
<% end %>
|
|
51
|
-
</code>
|
|
52
|
-
<style>
|
|
53
|
-
details[open] summary span { transform: rotate(90deg) !important; }
|
|
54
|
-
details summary::-webkit-details-marker { display: none; }
|
|
55
|
-
details summary::marker { display: none; }
|
|
56
|
-
</style>
|
|
57
|
-
|
|
58
|
-
<h3>Resources</h3>
|
|
59
|
-
<table>
|
|
60
|
-
<thead>
|
|
61
|
-
<th>Request path</th>
|
|
62
|
-
<th>Asset path</th>
|
|
63
|
-
<th>Media type</th>
|
|
64
|
-
</thead>
|
|
65
|
-
<tbody>
|
|
66
|
-
<% site.resources.each do |resource| %>
|
|
67
|
-
<tr>
|
|
68
|
-
<td><%= link_to resource.request_path, resource.request_path %></td>
|
|
69
|
-
<td><%= resource.asset.path %></td>
|
|
70
|
-
<td><%= resource.asset.mime_type %></td>
|
|
71
|
-
</tr>
|
|
72
|
-
<% end %>
|
|
73
|
-
</tbody>
|
|
74
|
-
</table>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<h2>Available resources</h2>
|
|
2
|
-
<table>
|
|
3
|
-
<thead>
|
|
4
|
-
<th>Request path</th>
|
|
5
|
-
<th>Asset path</th>
|
|
6
|
-
<th>Mime type</th>
|
|
7
|
-
</thead>
|
|
8
|
-
<tbody>
|
|
9
|
-
<% site.resources.each do |resource| %>
|
|
10
|
-
<tr>
|
|
11
|
-
<td><%= link_to resource.request_path, resource.request_path %></td>
|
|
12
|
-
<td><%= resource.asset.path %></td>
|
|
13
|
-
<td><%= resource.asset.mime_type %></td>
|
|
14
|
-
</tr>
|
|
15
|
-
<% end %>
|
|
16
|
-
</tbody>
|
|
17
|
-
</table>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<h2><%= @exception.class %></h2>
|
|
2
|
-
<h3><%= @exception.message %></h3>
|
|
3
|
-
|
|
4
|
-
<p>This error typically occurs when there's invalid YAML in the frontmatter of a page.</p>
|
|
5
|
-
|
|
6
|
-
<h3>Common causes</h3>
|
|
7
|
-
<ul>
|
|
8
|
-
<li>Missing closing <code>---</code> delimiter</li>
|
|
9
|
-
<li>Invalid YAML syntax (bad indentation, missing quotes around special characters)</li>
|
|
10
|
-
<li>Tabs instead of spaces for indentation</li>
|
|
11
|
-
<li>Special characters that need quoting: <code>: { } [ ] , & * # ? | - < > = ! % @ \</code></li>
|
|
12
|
-
</ul>
|
|
13
|
-
|
|
14
|
-
<h3>Backtrace</h3>
|
|
15
|
-
<code>
|
|
16
|
-
<pre><%= @exception.backtrace.join("\n") %></pre>
|
|
17
|
-
</code>
|
|
18
|
-
|
|
19
|
-
<h3>Resources</h3>
|
|
20
|
-
<table>
|
|
21
|
-
<thead>
|
|
22
|
-
<th>Request path</th>
|
|
23
|
-
<th>Asset path</th>
|
|
24
|
-
<th>Media type</th>
|
|
25
|
-
</thead>
|
|
26
|
-
<tbody>
|
|
27
|
-
<% site.resources.each do |resource| %>
|
|
28
|
-
<tr>
|
|
29
|
-
<td><%= link_to resource.request_path, resource.request_path %></td>
|
|
30
|
-
<td><%= resource.asset.path %></td>
|
|
31
|
-
<td><%= resource.asset.mime_type %></td>
|
|
32
|
-
</tr>
|
|
33
|
-
<% end %>
|
|
34
|
-
</tbody>
|
|
35
|
-
</table>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<h2><%= @exception.class %>: <%= @exception.message %> in <%= current_page.asset.path %></h2>
|
|
2
|
-
<h3>Source</h3>
|
|
3
|
-
|
|
4
|
-
<code>
|
|
5
|
-
<pre><%= current_page.asset.path %></pre>
|
|
6
|
-
</code>
|
|
7
|
-
|
|
8
|
-
<h3>Backtrace</h3>
|
|
9
|
-
<code>
|
|
10
|
-
<pre><%= @exception.backtrace.join("\n") %></pre>
|
|
11
|
-
</code>
|
|
12
|
-
|
|
13
|
-
<h3>Resources</h3>
|
|
14
|
-
<table>
|
|
15
|
-
<thead>
|
|
16
|
-
<th>Request path</th>
|
|
17
|
-
<th>Asset path</th>
|
|
18
|
-
<th>Media type</th>
|
|
19
|
-
</thead>
|
|
20
|
-
<tbody>
|
|
21
|
-
<% site.resources.each do |resource| %>
|
|
22
|
-
<tr>
|
|
23
|
-
<td><%= link_to resource.request_path, resource.request_path %></td>
|
|
24
|
-
<td><%= resource.asset.path %></td>
|
|
25
|
-
<td><%= resource.asset.mime_type %></td>
|
|
26
|
-
</tr>
|
|
27
|
-
<% end %>
|
|
28
|
-
</tbody>
|
|
29
|
-
</table>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg width="895px" height="895px" viewBox="0 0 895 895" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
3
|
-
<title>Sitepress logo</title>
|
|
4
|
-
<desc>A static and dynamic website builder</desc>
|
|
5
|
-
<defs></defs>
|
|
6
|
-
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
7
|
-
<g id="logo" fill="#ffffff">
|
|
8
|
-
<path d="M334.72,537.93 C325.82,530.6 313.41,526.88 297.83,526.88 L254.05,526.88 L254.05,618.4 L297.84,618.4 C313.41,618.4 325.84,614.63 334.72,607.2 C343.6,599.77 348.01,588.2 348.01,572.49 C348.01,556.78 343.54,545.2 334.72,537.93 L334.72,537.93 Z" id="Shape"></path>
|
|
9
|
-
<path d="M859.29,274.59 C770.211759,62.9463102 535.248488,-46.7589164 315.795351,20.8303235 C96.3422149,88.4195635 -36.1856795,311.308174 9.24445457,536.395018 C54.6745886,761.481862 263.278739,915.518494 491.767697,892.697877 C720.256655,869.87726 894.274848,677.625748 894.29,448 C894.373576,388.434111 882.470683,329.460375 859.29,274.59 L859.29,274.59 Z M573.53,155.3 C573.53,150.7 576.17,148.06 580.77,148.06 L684.32,148.06 C688.73,148.06 691.27,150.59 691.27,155.01 C691.27,159.79 688.74,162.54 684.32,162.54 L588,162.54 L588,249 L632.54,249 C636.95,249 639.48,251.74 639.48,256.53 C639.48,260.94 636.95,263.48 632.54,263.48 L588,263.48 L588,350.23 L684.32,350.23 C688.73,350.23 691.27,352.97 691.27,357.76 C691.27,362.17 688.74,364.71 684.32,364.71 L580.77,364.71 C576.17,364.71 573.53,362.07 573.53,357.47 L573.53,155.3 Z M401.53,89.3 C402.779293,87.9227726 404.571684,87.1655789 406.43,87.23 L553.75,87.23 C558.27,87.23 560.53,89.3666667 560.53,93.64 C560.53,98.4133333 558.27,100.8 553.75,100.8 L487.06,100.8 L487.06,355.91 C487.06,360.43 484.8,362.69 480.28,362.69 C475.76,362.69 473.5,360.43 473.5,355.91 L473.5,100.83 L406.43,100.83 C404.571176,100.897181 402.777669,100.139516 401.53,98.76 C399.018254,96.1222984 399.018254,91.9777016 401.53,89.34 L401.53,89.3 Z M361.08,93.63 C361.08,89.3633333 363.34,87.2266667 367.86,87.22 C372.38,87.2133333 374.64,89.35 374.64,93.63 L374.64,355.91 C374.64,360.43 372.38,362.69 367.86,362.69 C363.34,362.69 361.08,360.43 361.08,355.91 L361.08,93.63 Z M208.57,329.63 C210.15,327.79 211.7,326.89 213.29,326.89 C214.88,326.89 216.79,328.24 219.49,331.55 C221.62,334.16 224.09,337.02 226.84,340.05 C229.42,342.91 233.97,345.62 240.34,348.11 C247.586888,350.800137 255.270834,352.119225 263,352 C278.29,352 290,348.13 297.82,340.5 C305.64,332.87 309.58,321.57 309.58,306.88 C309.58,297.17 307.85,288.88 304.43,282.35 C301.410563,276.249269 296.784618,271.087433 291.05,267.42 C285.097567,263.784066 278.78788,260.768283 272.22,258.42 L251.58,251.15 C244.726443,248.889218 238.156396,245.84579 232,242.08 C225.705868,238.108684 220.588935,232.525633 217.18,225.91 C213.45,218.97 211.56,210.38 211.56,200.39 C211.56,190.23 213.44,181.39 217.16,174.03 C220.484889,167.084479 225.841413,161.312965 232.52,157.48 C238.378086,154.162981 244.677689,151.694992 251.23,150.15 C257.679927,148.734499 264.266659,148.036922 270.87,148.07 C281.863142,147.943398 292.800246,149.653588 303.23,153.13 C313.87,156.83 318.83,160.66 318.83,165.2 C318.754645,167.219913 317.98465,169.151963 316.65,170.67 C315.09,172.62 313.39,173.6 311.6,173.6 C310.78,173.6 309.49,173.37 304.51,170.6 C300.133975,168.352176 295.540237,166.556229 290.8,165.24 C284.225834,163.369844 277.414361,162.467037 270.58,162.56 C263.351008,162.469822 256.146813,163.422972 249.19,165.39 C243.12,167.2 237.67,171.04 233.01,176.8 C228.35,182.56 226.01,190.16 226.01,199.54 C226.01,207.46 227.72,214.2 231.09,219.54 C234.303387,224.772292 238.912858,229.004278 244.4,231.76 C250.482431,234.769407 256.763789,237.358797 263.2,239.51 C270.06,241.83 277.09,244.44 284.1,247.26 C291.011648,250.019216 297.587203,253.555254 303.7,257.8 C309.78,262.06 314.76,268.35 318.48,276.5 C322.2,284.65 324.04,294.5 324.04,305.98 C324.04,326.24 318.59,341.56 307.84,351.52 C297.09,361.48 282.53,366.45 264.45,366.45 C256.023647,366.517248 247.635543,365.310279 239.57,362.87 C231.93,360.49 225.72,357.61 221.11,354.3 C217.052927,351.515289 213.430344,348.145445 210.36,344.3 C207.64,340.72 206.36,337.7 206.36,335.09 C206.41612,333.079377 207.205725,331.158715 208.58,329.69 L208.57,329.63 Z M128.83,453.11 L296.96,413.11 C299.21742,412.571572 301.590658,413.277552 303.186737,414.962302 C304.782816,416.647052 305.359576,419.054959 304.7,421.28 L296,450.51 L453.2,413.11 C455.45742,412.571572 457.830658,413.277552 459.426737,414.962302 C461.022816,416.647052 461.599576,419.054959 460.94,421.28 L452.27,450.51 L609.47,413.11 C611.72742,412.571572 614.100658,413.277552 615.696737,414.962302 C617.292816,416.647052 617.869576,419.054959 617.21,421.28 L608.54,450.51 L765.74,413.11 C767.999705,412.574102 770.373657,413.284449 771.967611,414.973462 C773.561565,416.662475 774.133361,419.073551 773.467611,421.298462 C772.80186,423.523373 770.999705,425.224102 768.74,425.76 L600.61,465.76 C598.35258,466.298428 595.979342,465.592448 594.383263,463.907698 C592.787184,462.222948 592.210424,459.815041 592.87,457.59 L601.54,428.36 L444.34,465.76 C442.08258,466.298428 439.709342,465.592448 438.113263,463.907698 C436.517184,462.222948 435.940424,459.815041 436.6,457.59 L445.27,428.36 L288.07,465.76 C285.81258,466.298428 283.439342,465.592448 281.843263,463.907698 C280.247184,462.222948 279.670424,459.815041 280.33,457.59 L289,428.36 L131.8,465.76 C131.305299,465.878067 130.798593,465.93847 130.29,465.94 C126.983263,465.951866 124.194638,463.479226 123.810649,460.194838 C123.426659,456.910451 125.569705,453.861232 128.79,453.11 L128.83,453.11 Z M219.36,568.38 C219.36,586.66 214.64,600.38 205.36,609.03 C196.08,617.68 183.68,622.03 168.41,622.03 L131.51,622.03 L131.51,690.84 C131.51,695.17 128.92,697.76 124.59,697.76 C120.26,697.76 117.67,695.17 117.67,690.84 L117.67,518.64 C117.67,516.86 118.32,512.64 124.31,511.98 L124.58,511.98 L168.38,511.98 C183.63,511.98 196.06,516.32 205.32,524.89 C214.58,533.46 219.32,547.14 219.32,565.43 L219.36,568.38 Z M360.87,727.12 C361.533457,728.352283 361.903078,729.721252 361.95,731.12 C362.033205,733.000791 361.322464,734.830022 359.991243,736.161243 C358.660022,737.492464 356.830791,738.203205 354.95,738.12 C353.896221,738.11813 352.861519,737.838761 351.95,737.31 C350.899467,736.658391 350.007755,735.780345 349.34,734.74 C348.62,733.74 348.06,732.9 347.65,732.28 L347.49,731.96 L346.31,728.7 C345.91,727.92 345.65,727.45 345.49,727.17 L345.26,727.17 L344.84,726.35 L296.63,632.35 L254.05,632.35 L254.05,731.15 C254.05,735.65 251.57,738.15 247.05,738.15 C242.53,738.15 240.05,735.67 240.05,731.15 L240.05,519.6 C240.05,517.4 240.93,513.6 246.86,512.94 L297.78,512.94 C316.68,512.94 332.23,517.65 344,526.94 C355.77,536.23 361.89,551.64 361.89,572.49 C361.89,606.93 344.98,626.76 311.64,631.49 L360.87,727.12 Z M459,644.64 C463.246667,644.64 465.373333,647.016667 465.38,651.77 C465.38,656.023333 463.253333,658.15 459,658.15 L399,658.15 L399,775.22 L525.79,775.22 C530.043333,775.22 532.17,777.596667 532.17,782.35 C532.17,786.603333 530.043333,788.73 525.79,788.73 L392.2,788.73 C387.7,788.73 385.45,786.48 385.45,781.98 L385.45,521.19 C385.45,516.69 387.7,514.44 392.2,514.44 L525.79,514.44 C530.043333,514.44 532.17,516.566667 532.17,520.82 C532.17,525.573333 530.043333,527.95 525.79,527.95 L399,527.95 L399,644.65 L459,644.64 Z M643.77,724.49 C632.69,734.74 617.57,739.93 598.83,739.93 C590.082311,740.000953 581.374084,738.750178 573,736.22 C565.08,733.75 558.65,730.76 553.88,727.34 C549.685777,724.461626 545.941701,720.977041 542.77,717 C540.01,713.36 538.67,710.33 538.67,707.74 C538.734283,705.789092 539.509619,703.928997 540.85,702.51 C542.39,700.72 543.85,699.86 545.33,699.86 C546.81,699.86 548.69,701.25 551.33,704.51 C553.57,707.26 556.16,710.26 559.03,713.42 C561.9,716.58 566.61,719.36 573.36,722 C580.11,724.64 588.19,726 597.28,726 C613.41,726 625.79,721.9 634.07,713.81 C642.35,705.72 646.53,693.81 646.53,678.26 C646.53,668.02 644.7,659.26 641.08,652.36 C637.874282,645.8902 632.964805,640.41717 626.88,636.53 C620.626732,632.715387 613.998721,629.552171 607.1,627.09 L585.53,619.5 C578.407687,617.155538 571.579303,613.998039 565.18,610.09 C558.689278,605.99337 553.413379,600.234076 549.9,593.41 C546.05,586.24 544.1,577.36 544.1,567.02 C544.1,556.49 546.05,547.32 549.88,539.75 C553.71,532.18 559.06,526.39 565.69,522.75 C571.760835,519.311259 578.289388,516.752362 585.08,515.15 C591.7762,513.680181 598.61447,512.955774 605.47,512.99 C616.890766,512.856493 628.25367,514.630891 639.09,518.24 C650,522 655,525.78 655,530.24 C654.916423,532.213781 654.157409,534.098961 652.85,535.58 C651.34,537.47 649.72,538.42 648.03,538.42 C647.19,538.42 645.75,538.05 640.91,535.34 C636.30085,532.971571 631.462578,531.07848 626.47,529.69 C619.544449,527.721432 612.369222,526.771473 605.17,526.87 C597.558092,526.781289 589.97323,527.791706 582.65,529.87 C576.19,531.8 570.4,535.87 565.46,541.99 C560.52,548.11 558.04,556.19 558.04,566.12 C558.04,574.52 559.85,581.66 563.43,587.36 C566.838785,592.927174 571.736573,597.43009 577.57,600.36 C583.94981,603.517486 590.538592,606.233855 597.29,608.49 C604.46,610.91 611.79,613.63 619.1,616.57 C626.282202,619.451958 633.112871,623.142398 639.46,627.57 C645.73,631.96 650.86,638.45 654.71,646.87 C658.56,655.29 660.47,665.52 660.47,677.43 C660.51,698.35 654.87,714.21 643.76,724.49 L643.77,724.49 Z M765.92,686.38 C756.67,694.94 744.07,699.29 728.49,699.29 C721.242168,699.348298 714.027215,698.309885 707.09,696.21 C701.428334,694.592844 696.056089,692.095694 691.17,688.81 C687.654637,686.392813 684.517177,683.467661 681.86,680.13 C679.44,676.94 678.27,674.23 678.27,671.84 C678.322752,669.953085 679.062359,668.150293 680.35,666.77 C681.85,665.02 683.35,664.18 684.94,664.18 C686.53,664.18 688.35,665.41 690.81,668.43 C692.62,670.65 694.71,673.07 697.04,675.63 C699.16,677.97 702.92,680.21 708.24,682.29 C714.32226,684.514605 720.764973,685.589521 727.24,685.46 C740.04,685.46 749.82,682.24 756.33,675.89 C762.84,669.54 766.1,660.12 766.1,647.83 C766.1,639.7 764.65,632.83 761.8,627.32 C759.297736,622.247998 755.460166,617.95407 750.7,614.9 C745.678978,611.841159 740.357883,609.304592 734.82,607.33 L717.27,601.15 C711.38642,599.215288 705.745772,596.608041 700.46,593.38 C694.996887,589.94318 690.550437,585.110082 687.58,579.38 C684.35,573.38 682.71,565.93 682.71,557.3 C682.71,548.67 684.34,540.87 687.55,534.53 C690.448442,528.485526 695.11436,523.46391 700.93,520.13 C705.977623,517.274361 711.405115,515.149827 717.05,513.82 C722.592378,512.592976 728.253448,511.9826 733.93,512 C743.367043,511.889982 752.756198,513.356827 761.71,516.34 C771.11,519.61 775.49,523.1 775.49,527.34 C775.433713,529.229237 774.727297,531.041195 773.49,532.47 C772.02,534.31 770.37,535.25 768.6,535.25 C767.77,535.25 766.6,535.04 762.2,532.58 C758.525602,530.696709 754.669063,529.19229 750.69,528.09 C745.162798,526.517786 739.435902,525.759815 733.69,525.84 C727.607224,525.759866 721.544725,526.558095 715.69,528.21 C710.365486,529.922249 705.692777,533.223505 702.3,537.67 C698.48,542.39 696.54,548.74 696.54,556.53 C696.54,563.13 697.95,568.72 700.74,573.16 C703.393404,577.491303 707.202977,580.996109 711.74,583.28 C716.884382,585.823473 722.196793,588.011919 727.64,589.83 C733.5,591.83 739.5,594.04 745.46,596.44 C751.395098,598.808559 757.041415,601.844754 762.29,605.49 C767.58,609.19 771.89,614.65 775.12,621.7 C778.35,628.75 779.92,637.22 779.92,647.1 C779.95,664.54 775.23,677.76 765.92,686.38 L765.92,686.38 Z" id="Shape"></path>
|
|
10
|
-
<path d="M168.39,525.81 L131.51,525.81 L131.51,608.24 L168.39,608.24 C180.34,608.24 189.61,604.91 195.94,598.35 C202.27,591.79 205.52,581.68 205.52,568.35 L205.52,565.35 C205.52,552.06 202.3,542.03 195.95,535.52 C189.6,529.01 180.34,525.81 168.39,525.81 L168.39,525.81 Z" id="Shape"></path>
|
|
11
|
-
</g>
|
|
12
|
-
</g>
|
|
13
|
-
</svg>
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
body {
|
|
2
|
-
font-family: sans-serif;
|
|
3
|
-
margin: 2rem;
|
|
4
|
-
padding: 0;
|
|
5
|
-
background: hsl(23,39%,26%);
|
|
6
|
-
color: #fefefe;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
main {
|
|
10
|
-
color: #000;
|
|
11
|
-
background: #fefefe;
|
|
12
|
-
padding: 2rem;
|
|
13
|
-
border-radius: 0.5rem;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
header {
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-direction: row;
|
|
19
|
-
align-items: center;
|
|
20
|
-
margin-bottom: 1rem;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#logo {
|
|
24
|
-
max-width: 4rem;
|
|
25
|
-
max-height: 4rem;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#title {
|
|
29
|
-
margin-left: 1rem;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
footer {
|
|
33
|
-
margin: 4rem 1rem;
|
|
34
|
-
text-align: center;
|
|
35
|
-
}
|
|
36
|
-
footer a {
|
|
37
|
-
color: #fefefe;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
pre {
|
|
41
|
-
overflow: scroll;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
code {
|
|
45
|
-
display: block;
|
|
46
|
-
color: #000;
|
|
47
|
-
background: hsl(23,39%,90%);
|
|
48
|
-
padding: 1rem;
|
|
49
|
-
border-radius: 0.5rem;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
h2, a {
|
|
53
|
-
color: hsl(23,39%,26%);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
h3 {
|
|
57
|
-
color: hsl(23,39%,5%);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
table td, table th {
|
|
61
|
-
text-align: left;
|
|
62
|
-
padding: 0.25rem;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@media (prefers-color-scheme: dark) {
|
|
66
|
-
h2, a {
|
|
67
|
-
color: hsl(23,39%,90%);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
h3 {
|
|
71
|
-
color: hsl(23,39%,85%);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
main {
|
|
75
|
-
color: hsl(23,39%,85%);
|
|
76
|
-
background: hsl(23,39%,10%);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
code {
|
|
80
|
-
color: hsl(23,39%,85%);
|
|
81
|
-
background: hsl(23,39%,7%);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|