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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -1
  3. data/exe/wytch +6 -0
  4. data/lib/wytch/builder.rb +48 -0
  5. data/lib/wytch/cli.rb +94 -0
  6. data/lib/wytch/content_loader.rb +30 -0
  7. data/lib/wytch/once.rb +20 -0
  8. data/lib/wytch/page.rb +61 -0
  9. data/lib/wytch/reload_coordinator.rb +49 -0
  10. data/lib/wytch/server.rb +65 -0
  11. data/lib/wytch/site.rb +48 -0
  12. data/lib/wytch/site_code_loader_middleware.rb +18 -0
  13. data/lib/wytch/templates/Gemfile.tt +12 -0
  14. data/lib/wytch/templates/config.rb.tt +6 -0
  15. data/lib/wytch/templates/content/feed.rb.tt +4 -0
  16. data/lib/wytch/templates/content/index.rb.tt +6 -0
  17. data/lib/wytch/templates/content/posts/hello-world.rb.tt +12 -0
  18. data/lib/wytch/templates/content/sitemap.rb.tt +4 -0
  19. data/lib/wytch/templates/frontend/Main.res.tt +2 -0
  20. data/lib/wytch/templates/frontend/main.css.tt +1 -0
  21. data/lib/wytch/templates/gitignore.tt +4 -0
  22. data/lib/wytch/templates/package.json.tt +19 -0
  23. data/lib/wytch/templates/public/robots.txt.tt +2 -0
  24. data/lib/wytch/templates/rescript.json.tt +18 -0
  25. data/lib/wytch/templates/src/site/feed_helper.rb.tt +17 -0
  26. data/lib/wytch/templates/src/site/feed_view.rb.tt +42 -0
  27. data/lib/wytch/templates/src/site/home_view.rb.tt +29 -0
  28. data/lib/wytch/templates/src/site/layout.rb.tt +33 -0
  29. data/lib/wytch/templates/src/site/page.rb.tt +9 -0
  30. data/lib/wytch/templates/src/site/post_helpers.rb.tt +18 -0
  31. data/lib/wytch/templates/src/site/post_view.rb.tt +21 -0
  32. data/lib/wytch/templates/src/site/sitemap_helper.rb.tt +17 -0
  33. data/lib/wytch/templates/src/site/sitemap_view.rb.tt +27 -0
  34. data/lib/wytch/templates/vite.config.js.tt +26 -0
  35. data/lib/wytch/version.rb +1 -1
  36. data/lib/wytch.rb +23 -2
  37. metadata +135 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61bbb9530546971a617d753468cc6213d7fc11fc64c79242ac6ba81929b48cb1
4
- data.tar.gz: 59a823a9af8dc811e2deaf940eaae0b1f08c8cdb2a746fd4558d039c1de2623b
3
+ metadata.gz: 662c7632994e850e71fce70842f153b11d7e56f8877df6156094c070213a68a6
4
+ data.tar.gz: 546b863eb417cd7ada91d725a1def5662aae4182e9c53762ab98758c574f103b
5
5
  SHA512:
6
- metadata.gz: bd3d5170c33314e16a993d3fbc6bda029f42a3d09d09ae1bec03c2dbfe3a2823c56acff5feeda33cb7f90367db1ac3b6433e48d05b4422b479af4c7e7b0963e0
7
- data.tar.gz: c9ead1e98b30442aabe805f98a77c95ab0d6e005c79633b03d0d0dcecf64fdfd0be3a7439a317e2fb049c89fc097ef2d68e736d58bceac0a3c6a6587611c79fd
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
- FIXME
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,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "wytch"
5
+
6
+ Wytch::CLI.start(ARGV)
@@ -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
@@ -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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ Wytch.configure do |site|
4
+ site.page_class = "<%= @site_module %>::Page"
5
+ # site.base_url = "https://example.com"
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ add <%= @site_module %>::FeedHelper
4
+ view <%= @site_module %>::FeedView
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ view <%= @site_module %>::HomeView
4
+
5
+ @metadata[:title] = "Welcome to Wytch"
6
+ @metadata[:description] = "Your static site generator"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ add <%= @site_module %>::PostHelpers
4
+ view <%= @site_module %>::PostView
5
+
6
+ @metadata[:title] = "Hello World"
7
+
8
+ content_markdown <<~MD
9
+ This is your first blog post.
10
+
11
+ Write your content in **markdown**.
12
+ MD
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ add <%= @site_module %>::SitemapHelper
4
+ view <%= @site_module %>::SitemapView
@@ -0,0 +1,2 @@
1
+ // Main entry point for ReScript code
2
+ Console.log("Hello from <%= @site_module %>!")
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,4 @@
1
+ build/
2
+ node_modules/
3
+ lib/
4
+ .vite/
@@ -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,2 @@
1
+ User-agent: *
2
+ Allow: /
@@ -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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= @site_module %>
4
+ module FeedHelper
5
+ def path
6
+ "/feed.xml"
7
+ end
8
+
9
+ def build_path
10
+ "feed.xml"
11
+ end
12
+
13
+ def include_in_sitemap?
14
+ false
15
+ end
16
+ end
17
+ end
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= @site_module %>
4
+ class Page < Wytch::Page
5
+ def blog_post?
6
+ false
7
+ end
8
+ end
9
+ 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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= @site_module %>
4
+ module SitemapHelper
5
+ def path
6
+ "/sitemap.xml"
7
+ end
8
+
9
+ def build_path
10
+ "sitemap.xml"
11
+ end
12
+
13
+ def include_in_sitemap?
14
+ false
15
+ end
16
+ end
17
+ 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wytch
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/wytch.rb CHANGED
@@ -1,8 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "wytch/version"
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
- # Your code goes here...
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.1.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.7.0
179
+ rubygems_version: 3.6.9
49
180
  specification_version: 4
50
181
  summary: A minimal static site generator
51
182
  test_files: []