wytch 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +23 -1
- data/exe/wytch +6 -0
- data/lib/wytch/builder.rb +48 -0
- data/lib/wytch/cli.rb +94 -0
- data/lib/wytch/content_loader.rb +30 -0
- data/lib/wytch/once.rb +20 -0
- data/lib/wytch/page.rb +61 -0
- data/lib/wytch/reload_coordinator.rb +49 -0
- data/lib/wytch/server.rb +65 -0
- data/lib/wytch/site.rb +48 -0
- data/lib/wytch/site_code_loader_middleware.rb +18 -0
- data/lib/wytch/templates/Gemfile.tt +12 -0
- data/lib/wytch/templates/config.rb.tt +6 -0
- data/lib/wytch/templates/content/feed.rb.tt +4 -0
- data/lib/wytch/templates/content/index.rb.tt +6 -0
- data/lib/wytch/templates/content/posts/hello-world.rb.tt +12 -0
- data/lib/wytch/templates/content/sitemap.rb.tt +4 -0
- data/lib/wytch/templates/frontend/Main.res.tt +2 -0
- data/lib/wytch/templates/frontend/main.css.tt +1 -0
- data/lib/wytch/templates/gitignore.tt +4 -0
- data/lib/wytch/templates/package.json.tt +19 -0
- data/lib/wytch/templates/public/robots.txt.tt +2 -0
- data/lib/wytch/templates/rescript.json.tt +18 -0
- data/lib/wytch/templates/src/site/feed_helper.rb.tt +17 -0
- data/lib/wytch/templates/src/site/feed_view.rb.tt +42 -0
- data/lib/wytch/templates/src/site/home_view.rb.tt +29 -0
- data/lib/wytch/templates/src/site/layout.rb.tt +33 -0
- data/lib/wytch/templates/src/site/page.rb.tt +9 -0
- data/lib/wytch/templates/src/site/post_helpers.rb.tt +18 -0
- data/lib/wytch/templates/src/site/post_view.rb.tt +21 -0
- data/lib/wytch/templates/src/site/sitemap_helper.rb.tt +17 -0
- data/lib/wytch/templates/src/site/sitemap_view.rb.tt +27 -0
- data/lib/wytch/templates/vite.config.js.tt +26 -0
- data/lib/wytch/version.rb +1 -1
- data/lib/wytch.rb +23 -2
- metadata +135 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 662c7632994e850e71fce70842f153b11d7e56f8877df6156094c070213a68a6
|
|
4
|
+
data.tar.gz: 546b863eb417cd7ada91d725a1def5662aae4182e9c53762ab98758c574f103b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a8a7bc87f19981aa54f33132f5cb5e9adf7799c380187f2b5db8a0756feab7f2c6e158eeaeeaef2d6bccb95188f8ff5e561e66b5bf4c8dda70df56859f8c137
|
|
7
|
+
data.tar.gz: ac6aae6cf98f82c74dd1b11ba1d814d03cbbb9b9ab85c6c2f738fc300632ff99602c571e436554e58b11aa7d47fb94c99f9532e41eb77116ccd7fe37a9a56dca
|
data/README.md
CHANGED
|
@@ -11,7 +11,29 @@ gem install wytch
|
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Generate a new site like this:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
wytch new my-site
|
|
18
|
+
cd my-site
|
|
19
|
+
bundle install
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Run the development servers with:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run dev # Runs the vite dev server for assets
|
|
27
|
+
wytch server # Runes the Wytch dev server
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then visit `http://localhost:6969` to see your site.
|
|
31
|
+
|
|
32
|
+
Once you are ready to build for production:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
wytch build # Builds assets and generates your static site in the build directory.
|
|
36
|
+
```
|
|
15
37
|
|
|
16
38
|
## Development
|
|
17
39
|
|
data/exe/wytch
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Wytch
|
|
6
|
+
class Builder
|
|
7
|
+
OUTPUT_DIR = "build"
|
|
8
|
+
|
|
9
|
+
def build
|
|
10
|
+
ENV["RACK_ENV"] = "production"
|
|
11
|
+
Site.load!
|
|
12
|
+
|
|
13
|
+
Wytch.site.site_code_loader.eager_load
|
|
14
|
+
Wytch.site.load_content
|
|
15
|
+
|
|
16
|
+
FileUtils.mkdir(OUTPUT_DIR) unless Dir.exist?(OUTPUT_DIR)
|
|
17
|
+
|
|
18
|
+
Wytch.site.pages.values.each do |page|
|
|
19
|
+
build_path = File.join(OUTPUT_DIR, page.build_path)
|
|
20
|
+
|
|
21
|
+
puts "Building #{page.path} → #{build_path}"
|
|
22
|
+
|
|
23
|
+
FileUtils.mkdir_p File.dirname(build_path)
|
|
24
|
+
|
|
25
|
+
File.write build_path, page.render
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
copy_public_files
|
|
29
|
+
copy_vite_assets
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def copy_public_files
|
|
35
|
+
return unless Dir.exist?("public")
|
|
36
|
+
|
|
37
|
+
FileUtils.cp_r "public/.", OUTPUT_DIR, verbose: true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def copy_vite_assets
|
|
41
|
+
vite_output = File.join(OUTPUT_DIR, "assets")
|
|
42
|
+
return unless Dir.exist?(vite_output)
|
|
43
|
+
|
|
44
|
+
# Vite already builds to build/assets, so assets are already in place
|
|
45
|
+
puts "Vite assets ready at #{vite_output}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/wytch/cli.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Wytch
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
include Thor::Actions
|
|
8
|
+
|
|
9
|
+
def self.source_root
|
|
10
|
+
File.expand_path("templates", __dir__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "new NAME", "Create a new Wytch site"
|
|
14
|
+
method_option :local, type: :boolean, default: false, desc: "Use local Wytch gem for development"
|
|
15
|
+
def new(name)
|
|
16
|
+
@local_wytch_path = File.expand_path("../..", __dir__) if options[:local]
|
|
17
|
+
|
|
18
|
+
# Extract site name and create module constant
|
|
19
|
+
@site_name = File.basename(name)
|
|
20
|
+
@site_module = classify(@site_name)
|
|
21
|
+
|
|
22
|
+
empty_directory(name)
|
|
23
|
+
template("Gemfile.tt", "#{name}/Gemfile")
|
|
24
|
+
template("config.rb.tt", "#{name}/config.rb")
|
|
25
|
+
template("gitignore.tt", "#{name}/.gitignore")
|
|
26
|
+
|
|
27
|
+
# Asset pipeline configuration
|
|
28
|
+
template("package.json.tt", "#{name}/package.json")
|
|
29
|
+
template("vite.config.js.tt", "#{name}/vite.config.js")
|
|
30
|
+
template("rescript.json.tt", "#{name}/rescript.json")
|
|
31
|
+
|
|
32
|
+
# Assets directory
|
|
33
|
+
empty_directory("#{name}/assets")
|
|
34
|
+
template("frontend/Main.res.tt", "#{name}/assets/Main.res")
|
|
35
|
+
template("frontend/main.css.tt", "#{name}/assets/main.css")
|
|
36
|
+
|
|
37
|
+
# Content directory
|
|
38
|
+
empty_directory("#{name}/content")
|
|
39
|
+
template("content/index.rb.tt", "#{name}/content/index.rb")
|
|
40
|
+
empty_directory("#{name}/content/posts")
|
|
41
|
+
template("content/posts/hello-world.rb.tt", "#{name}/content/posts/hello-world.rb")
|
|
42
|
+
template("content/sitemap.rb.tt", "#{name}/content/sitemap.rb")
|
|
43
|
+
template("content/feed.rb.tt", "#{name}/content/feed.rb")
|
|
44
|
+
|
|
45
|
+
# Src directory with namespaced code
|
|
46
|
+
empty_directory("#{name}/src")
|
|
47
|
+
empty_directory("#{name}/src/#{@site_name}")
|
|
48
|
+
template("src/site/page.rb.tt", "#{name}/src/#{@site_name}/page.rb")
|
|
49
|
+
template("src/site/layout.rb.tt", "#{name}/src/#{@site_name}/layout.rb")
|
|
50
|
+
template("src/site/home_view.rb.tt", "#{name}/src/#{@site_name}/home_view.rb")
|
|
51
|
+
template("src/site/post_view.rb.tt", "#{name}/src/#{@site_name}/post_view.rb")
|
|
52
|
+
template("src/site/post_helpers.rb.tt", "#{name}/src/#{@site_name}/post_helpers.rb")
|
|
53
|
+
template("src/site/sitemap_view.rb.tt", "#{name}/src/#{@site_name}/sitemap_view.rb")
|
|
54
|
+
template("src/site/sitemap_helper.rb.tt", "#{name}/src/#{@site_name}/sitemap_helper.rb")
|
|
55
|
+
template("src/site/feed_view.rb.tt", "#{name}/src/#{@site_name}/feed_view.rb")
|
|
56
|
+
template("src/site/feed_helper.rb.tt", "#{name}/src/#{@site_name}/feed_helper.rb")
|
|
57
|
+
|
|
58
|
+
# Public directory for static assets
|
|
59
|
+
empty_directory("#{name}/public")
|
|
60
|
+
template("public/robots.txt.tt", "#{name}/public/robots.txt")
|
|
61
|
+
|
|
62
|
+
say "Created new Wytch site in #{name}/", :green
|
|
63
|
+
say "\nNext steps:", :green
|
|
64
|
+
say " cd #{name}"
|
|
65
|
+
say " bundle install"
|
|
66
|
+
say " npm install"
|
|
67
|
+
say "\nTo start developing:"
|
|
68
|
+
say " npm run dev # Start Vite dev server (Terminal 1)"
|
|
69
|
+
say " wytch server # Start Wytch dev server (Terminal 2)"
|
|
70
|
+
say "\nTo build for production:"
|
|
71
|
+
say " npm run build # Build assets with Vite"
|
|
72
|
+
say " wytch build # Build static site"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
desc "server", "Start a development server"
|
|
76
|
+
method_option :port, type: :numeric, default: 6969, aliases: "-p", desc: "Port to run the server on"
|
|
77
|
+
method_option :host, type: :string, default: "localhost", aliases: "-h", desc: "Host to bind the server to"
|
|
78
|
+
def server
|
|
79
|
+
Server.new(options).start
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
desc "build", "Build the static site"
|
|
83
|
+
def build
|
|
84
|
+
system("npm run build") || abort("Asset build failed")
|
|
85
|
+
Builder.new.build
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def classify(name)
|
|
91
|
+
name.split("_").map(&:capitalize).join
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wytch
|
|
4
|
+
class ContentLoader
|
|
5
|
+
def load_content
|
|
6
|
+
pages = {}
|
|
7
|
+
|
|
8
|
+
Dir.glob(File.join("**", "*.rb"), base: content_dir).each do |file_path|
|
|
9
|
+
page = load_page(file_path)
|
|
10
|
+
pages[page.path] = page
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
pages
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def load_page(file_path)
|
|
19
|
+
page_class.new(file_path:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def page_class
|
|
23
|
+
Object.const_get(Wytch.site.page_class)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def content_dir
|
|
27
|
+
Wytch.site.content_dir
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/wytch/once.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wytch
|
|
4
|
+
class Once
|
|
5
|
+
def initialize(&block)
|
|
6
|
+
@block = block
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
@mutex&.synchronize do
|
|
12
|
+
return unless @mutex
|
|
13
|
+
|
|
14
|
+
@block.call
|
|
15
|
+
|
|
16
|
+
@mutex = nil
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/wytch/page.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wytch
|
|
4
|
+
class Page
|
|
5
|
+
def initialize(file_path:)
|
|
6
|
+
@file_path = file_path
|
|
7
|
+
@metadata = {}
|
|
8
|
+
@view_class = nil
|
|
9
|
+
|
|
10
|
+
source_path = File.join(Wytch.site.content_dir, @file_path)
|
|
11
|
+
|
|
12
|
+
instance_eval File.read(source_path), source_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :metadata
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
@view_class.new(self).call
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def path
|
|
22
|
+
if virtual_path == "index"
|
|
23
|
+
"/"
|
|
24
|
+
else
|
|
25
|
+
"/#{virtual_path}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def virtual_path
|
|
30
|
+
@file_path.delete_prefix("#{Wytch.site.content_dir}/").delete_suffix(".rb")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_path
|
|
34
|
+
"#{virtual_path}/index.html"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def add(helper_module)
|
|
38
|
+
extend helper_module
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def view(view_class)
|
|
42
|
+
@view_class = view_class
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def include_in_sitemap?
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def last_modified
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def change_frequency
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def priority
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/atomic/read_write_lock"
|
|
4
|
+
require "listen"
|
|
5
|
+
|
|
6
|
+
module Wytch
|
|
7
|
+
class ReloadCoordinator
|
|
8
|
+
attr_reader :reload_lock
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@reload_lock = Concurrent::ReadWriteLock.new
|
|
12
|
+
|
|
13
|
+
@site_code_dirty = true
|
|
14
|
+
@content_dirty = true
|
|
15
|
+
|
|
16
|
+
@start_site_code_listener = Once.new do
|
|
17
|
+
Listen.to(Wytch.site.site_code_path) do
|
|
18
|
+
@site_code_dirty = true
|
|
19
|
+
end.start
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@start_content_listener = Once.new do
|
|
23
|
+
Listen.to(Wytch.site.content_dir) do
|
|
24
|
+
@content_dirty = true
|
|
25
|
+
end.start
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reload!
|
|
30
|
+
@start_site_code_listener&.call
|
|
31
|
+
@start_content_listener&.call
|
|
32
|
+
|
|
33
|
+
return unless @site_code_dirty || @content_dirty
|
|
34
|
+
|
|
35
|
+
reload_lock.with_write_lock do
|
|
36
|
+
if @site_code_dirty
|
|
37
|
+
# Site code changed: reload site code then reload content
|
|
38
|
+
@site_code_dirty = false
|
|
39
|
+
Wytch.site.site_code_loader.reload
|
|
40
|
+
Wytch.site.load_content
|
|
41
|
+
elsif @content_dirty
|
|
42
|
+
# Only content changed: just reload content
|
|
43
|
+
@content_dirty = false
|
|
44
|
+
Wytch.site.load_content
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/wytch/server.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
|
|
5
|
+
module Wytch
|
|
6
|
+
class Server
|
|
7
|
+
attr_reader :options
|
|
8
|
+
|
|
9
|
+
def initialize(options = {})
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
ENV["RACK_ENV"] = "development"
|
|
15
|
+
Site.load!
|
|
16
|
+
|
|
17
|
+
require "puma"
|
|
18
|
+
require "puma/server"
|
|
19
|
+
|
|
20
|
+
port = options[:port] || 6969
|
|
21
|
+
host = options[:host] || "localhost"
|
|
22
|
+
|
|
23
|
+
puts "Starting Wytch development server on http://#{host}:#{port}"
|
|
24
|
+
puts "Press Ctrl+C to stop"
|
|
25
|
+
|
|
26
|
+
server = Puma::Server.new(app)
|
|
27
|
+
server.add_tcp_listener(host, port)
|
|
28
|
+
server.run.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def app
|
|
34
|
+
base_app = lambda { |env|
|
|
35
|
+
path = env["PATH_INFO"]
|
|
36
|
+
|
|
37
|
+
# Otherwise try to serve a page
|
|
38
|
+
page = Wytch.site.pages[path]
|
|
39
|
+
|
|
40
|
+
if page
|
|
41
|
+
body = page.render
|
|
42
|
+
[
|
|
43
|
+
200,
|
|
44
|
+
{"content-type" => "text/html"},
|
|
45
|
+
[body]
|
|
46
|
+
]
|
|
47
|
+
else
|
|
48
|
+
[
|
|
49
|
+
404,
|
|
50
|
+
{"content-type" => "text/html"},
|
|
51
|
+
["<html><body><h1>404 Not Found</h1><p>Page not found: #{path}</p></body></html>"]
|
|
52
|
+
]
|
|
53
|
+
end
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
reload_coordinator = ReloadCoordinator.new
|
|
57
|
+
|
|
58
|
+
Rack::Builder.new do
|
|
59
|
+
use Rack::Static, urls: [""], root: "public", cascade: true
|
|
60
|
+
|
|
61
|
+
run SiteCodeLoaderMiddleware.new(base_app, reload_coordinator)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/wytch/site.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wytch
|
|
4
|
+
class Site
|
|
5
|
+
def self.load!
|
|
6
|
+
if File.exist?(config_file = File.join(Dir.pwd, "config.rb"))
|
|
7
|
+
load config_file
|
|
8
|
+
else
|
|
9
|
+
puts "Warning: config.rb not found in current directory"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@inflections = {}
|
|
15
|
+
@content_dir = "content"
|
|
16
|
+
@site_code_path = "src"
|
|
17
|
+
@page_class = "Wytch::Page"
|
|
18
|
+
@pages = {}
|
|
19
|
+
@base_url = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :inflections
|
|
23
|
+
attr_accessor :content_dir, :site_code_path, :page_class, :pages, :base_url
|
|
24
|
+
|
|
25
|
+
def inflect(inflections_hash)
|
|
26
|
+
@inflections.merge!(inflections_hash)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def site_code_loader
|
|
30
|
+
@site_code_loader ||= begin
|
|
31
|
+
loader = Zeitwerk::Loader.new
|
|
32
|
+
loader.push_dir @site_code_path if Dir.exist?(@site_code_path)
|
|
33
|
+
loader.enable_reloading
|
|
34
|
+
loader.inflector.inflect(@inflections) if @inflections.any?
|
|
35
|
+
loader.setup
|
|
36
|
+
loader
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def content_loader
|
|
41
|
+
@content_loader ||= ContentLoader.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def load_content
|
|
45
|
+
@pages = content_loader.load_content
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wytch
|
|
4
|
+
class SiteCodeLoaderMiddleware
|
|
5
|
+
def initialize(app, coordinator)
|
|
6
|
+
@app = app
|
|
7
|
+
@coordinator = coordinator
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(env)
|
|
11
|
+
@coordinator.reload!
|
|
12
|
+
|
|
13
|
+
@coordinator.reload_lock.with_read_lock {
|
|
14
|
+
@app.call(env)
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
<% if @local_wytch_path -%>
|
|
6
|
+
gem "wytch", path: "<%= @local_wytch_path %>"
|
|
7
|
+
<% else -%>
|
|
8
|
+
gem "wytch", "~> <%= Gem::Version.new(Wytch::VERSION).segments[0..1].join('.') %>"
|
|
9
|
+
<% end -%>
|
|
10
|
+
|
|
11
|
+
gem "builder", "~> 3.3"
|
|
12
|
+
gem "commonmarker", "~> 2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= @site_name %>",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@jihchi/vite-plugin-rescript": "^7.1.0",
|
|
12
|
+
"rescript": "^11.1.4",
|
|
13
|
+
"tailwindcss": "^4.1.0",
|
|
14
|
+
"vite": "^7.2.2"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@rescript/core": "^1.6.1"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= @site_name %>",
|
|
3
|
+
"sources": [
|
|
4
|
+
{
|
|
5
|
+
"dir": "assets",
|
|
6
|
+
"subdirs": true
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"package-specs": [
|
|
10
|
+
{
|
|
11
|
+
"module": "esmodule",
|
|
12
|
+
"in-source": false
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"suffix": ".res.js",
|
|
16
|
+
"bs-dependencies": ["@rescript/core"],
|
|
17
|
+
"bsc-flags": ["-open RescriptCore"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
class FeedView
|
|
5
|
+
def initialize(page)
|
|
6
|
+
@page = page
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
require "builder"
|
|
11
|
+
|
|
12
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
|
13
|
+
xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
|
|
14
|
+
xml.feed xmlns: "http://www.w3.org/2005/Atom" do
|
|
15
|
+
xml.title "Blog"
|
|
16
|
+
xml.link href: "#{Wytch.site.base_url}/feed.xml", rel: "self"
|
|
17
|
+
xml.link href: Wytch.site.base_url
|
|
18
|
+
xml.updated posts.first&.last_modified || Time.now.iso8601
|
|
19
|
+
xml.id Wytch.site.base_url
|
|
20
|
+
|
|
21
|
+
posts.each do |post|
|
|
22
|
+
xml.entry do
|
|
23
|
+
xml.title post.metadata[:title]
|
|
24
|
+
xml.link href: "#{Wytch.site.base_url}#{post.path}"
|
|
25
|
+
xml.id "#{Wytch.site.base_url}#{post.path}"
|
|
26
|
+
xml.updated post.last_modified || Time.now.iso8601
|
|
27
|
+
xml.content post.content_html, type: "html" if post.respond_to?(:content_html)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def posts
|
|
36
|
+
Wytch.site.pages.values
|
|
37
|
+
.select(&:blog_post?)
|
|
38
|
+
.sort_by { |p| p.last_modified || "" }
|
|
39
|
+
.reverse
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
class HomeView < Phlex::HTML
|
|
5
|
+
def initialize(page)
|
|
6
|
+
@page = page
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template
|
|
10
|
+
render Layout.new(@page) do
|
|
11
|
+
h1 { @page.metadata[:title] }
|
|
12
|
+
|
|
13
|
+
h2 { "Posts" }
|
|
14
|
+
|
|
15
|
+
ul do
|
|
16
|
+
posts.each do |post|
|
|
17
|
+
li { a(href: post.path) { post.metadata[:title] } }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def posts
|
|
26
|
+
Wytch.site.pages.values.select { |p| p.path.start_with?("/posts/") }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
class Layout < Phlex::HTML
|
|
5
|
+
def initialize(page)
|
|
6
|
+
@page = page
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template(&block)
|
|
10
|
+
doctype
|
|
11
|
+
|
|
12
|
+
html do
|
|
13
|
+
head do
|
|
14
|
+
title { @page.metadata[:title] }
|
|
15
|
+
meta name: "description", content: @page.metadata[:description] if @page.metadata[:description]
|
|
16
|
+
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
|
17
|
+
|
|
18
|
+
# Vite assets
|
|
19
|
+
if ENV["RACK_ENV"] == "development"
|
|
20
|
+
script src: "http://localhost:6970/@vite/client", type: "module"
|
|
21
|
+
link rel: "stylesheet", href: "http://localhost:6970/assets/main.css"
|
|
22
|
+
script src: "http://localhost:6970/assets/Main.res", type: "module"
|
|
23
|
+
else
|
|
24
|
+
link rel: "stylesheet", href: "/assets/main.css"
|
|
25
|
+
script src: "/assets/app.js", type: "module"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
body(&block)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
module PostHelpers
|
|
5
|
+
def blog_post?
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def content_markdown(markdown)
|
|
10
|
+
@content_markdown = markdown
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def content_html
|
|
14
|
+
require "commonmarker"
|
|
15
|
+
Commonmarker.to_html(@content_markdown || "")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
class PostView < Phlex::HTML
|
|
5
|
+
def initialize(page)
|
|
6
|
+
@page = page
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def view_template
|
|
10
|
+
render Layout.new(@page) do
|
|
11
|
+
article do
|
|
12
|
+
h1 { @page.metadata[:title] }
|
|
13
|
+
|
|
14
|
+
unsafe_raw @page.content_html
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
a(href: "/") { "Back to home" }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @site_module %>
|
|
4
|
+
class SitemapView
|
|
5
|
+
def initialize(page)
|
|
6
|
+
@page = page
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
require "builder"
|
|
11
|
+
|
|
12
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
|
13
|
+
xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
|
|
14
|
+
xml.urlset xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9" do
|
|
15
|
+
Wytch.site.pages.values.each do |page|
|
|
16
|
+
next unless page.include_in_sitemap?
|
|
17
|
+
xml.url do
|
|
18
|
+
xml.loc "#{Wytch.site.base_url}#{page.path}"
|
|
19
|
+
xml.lastmod page.last_modified if page.last_modified
|
|
20
|
+
xml.changefreq page.change_frequency if page.change_frequency
|
|
21
|
+
xml.priority page.priority if page.priority
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import createReScriptPlugin from "@jihchi/vite-plugin-rescript";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
createReScriptPlugin({
|
|
7
|
+
loader: {
|
|
8
|
+
suffix: ".res.js",
|
|
9
|
+
},
|
|
10
|
+
}),
|
|
11
|
+
],
|
|
12
|
+
build: {
|
|
13
|
+
outDir: "build/assets",
|
|
14
|
+
manifest: true,
|
|
15
|
+
rollupOptions: {
|
|
16
|
+
input: {
|
|
17
|
+
main: "./assets/main.css",
|
|
18
|
+
app: "./assets/Main.res",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
server: {
|
|
23
|
+
port: 6970,
|
|
24
|
+
strictPort: false,
|
|
25
|
+
},
|
|
26
|
+
});
|
data/lib/wytch/version.rb
CHANGED
data/lib/wytch.rb
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
require "phlex"
|
|
5
|
+
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
|
7
|
+
loader.inflector.inflect("cli" => "CLI")
|
|
8
|
+
loader.setup
|
|
4
9
|
|
|
5
10
|
module Wytch
|
|
6
11
|
class Error < StandardError; end
|
|
7
|
-
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def site
|
|
15
|
+
@site ||= Site.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield(site) if block_given?
|
|
20
|
+
site
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset_site!
|
|
24
|
+
@site = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
8
27
|
end
|
|
28
|
+
|
|
29
|
+
loader.eager_load
|
metadata
CHANGED
|
@@ -1,18 +1,117 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wytch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jared Norman
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: concurrent-ruby
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: listen
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.9'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.9'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: phlex
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.11'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.11'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: puma
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '6.4'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '6.4'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rack
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: thor
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.4'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.4'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: zeitwerk
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.6'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.6'
|
|
12
110
|
description: A minimal static site generator
|
|
13
111
|
email:
|
|
14
112
|
- jared@super.gd
|
|
15
|
-
executables:
|
|
113
|
+
executables:
|
|
114
|
+
- wytch
|
|
16
115
|
extensions: []
|
|
17
116
|
extra_rdoc_files: []
|
|
18
117
|
files:
|
|
@@ -21,7 +120,39 @@ files:
|
|
|
21
120
|
- LICENSE.txt
|
|
22
121
|
- README.md
|
|
23
122
|
- Rakefile
|
|
123
|
+
- exe/wytch
|
|
24
124
|
- lib/wytch.rb
|
|
125
|
+
- lib/wytch/builder.rb
|
|
126
|
+
- lib/wytch/cli.rb
|
|
127
|
+
- lib/wytch/content_loader.rb
|
|
128
|
+
- lib/wytch/once.rb
|
|
129
|
+
- lib/wytch/page.rb
|
|
130
|
+
- lib/wytch/reload_coordinator.rb
|
|
131
|
+
- lib/wytch/server.rb
|
|
132
|
+
- lib/wytch/site.rb
|
|
133
|
+
- lib/wytch/site_code_loader_middleware.rb
|
|
134
|
+
- lib/wytch/templates/Gemfile.tt
|
|
135
|
+
- lib/wytch/templates/config.rb.tt
|
|
136
|
+
- lib/wytch/templates/content/feed.rb.tt
|
|
137
|
+
- lib/wytch/templates/content/index.rb.tt
|
|
138
|
+
- lib/wytch/templates/content/posts/hello-world.rb.tt
|
|
139
|
+
- lib/wytch/templates/content/sitemap.rb.tt
|
|
140
|
+
- lib/wytch/templates/frontend/Main.res.tt
|
|
141
|
+
- lib/wytch/templates/frontend/main.css.tt
|
|
142
|
+
- lib/wytch/templates/gitignore.tt
|
|
143
|
+
- lib/wytch/templates/package.json.tt
|
|
144
|
+
- lib/wytch/templates/public/robots.txt.tt
|
|
145
|
+
- lib/wytch/templates/rescript.json.tt
|
|
146
|
+
- lib/wytch/templates/src/site/feed_helper.rb.tt
|
|
147
|
+
- lib/wytch/templates/src/site/feed_view.rb.tt
|
|
148
|
+
- lib/wytch/templates/src/site/home_view.rb.tt
|
|
149
|
+
- lib/wytch/templates/src/site/layout.rb.tt
|
|
150
|
+
- lib/wytch/templates/src/site/page.rb.tt
|
|
151
|
+
- lib/wytch/templates/src/site/post_helpers.rb.tt
|
|
152
|
+
- lib/wytch/templates/src/site/post_view.rb.tt
|
|
153
|
+
- lib/wytch/templates/src/site/sitemap_helper.rb.tt
|
|
154
|
+
- lib/wytch/templates/src/site/sitemap_view.rb.tt
|
|
155
|
+
- lib/wytch/templates/vite.config.js.tt
|
|
25
156
|
- lib/wytch/version.rb
|
|
26
157
|
- sig/wytch.rbs
|
|
27
158
|
homepage: https://github.com/SuperGoodSoft/wytch
|
|
@@ -45,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
176
|
- !ruby/object:Gem::Version
|
|
46
177
|
version: '0'
|
|
47
178
|
requirements: []
|
|
48
|
-
rubygems_version: 3.
|
|
179
|
+
rubygems_version: 3.6.9
|
|
49
180
|
specification_version: 4
|
|
50
181
|
summary: A minimal static site generator
|
|
51
182
|
test_files: []
|