staticky 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +163 -0
- data/Rakefile +12 -0
- data/bin/staticky +7 -0
- data/lib/staticky/builder.rb +62 -0
- data/lib/staticky/cli.rb +71 -0
- data/lib/staticky/container.rb +26 -0
- data/lib/staticky/deps.rb +5 -0
- data/lib/staticky/environment.rb +8 -0
- data/lib/staticky/error.rb +5 -0
- data/lib/staticky/filesystem.rb +29 -0
- data/lib/staticky/generator.rb +63 -0
- data/lib/staticky/phlex/view_helpers.rb +16 -0
- data/lib/staticky/resource.rb +25 -0
- data/lib/staticky/router/definition.rb +49 -0
- data/lib/staticky/router.rb +35 -0
- data/lib/staticky/server.rb +48 -0
- data/lib/staticky/version.rb +5 -0
- data/lib/staticky/view_context.rb +17 -0
- data/lib/staticky.rb +51 -0
- data/site_template/.dockerignore +4 -0
- data/site_template/.gitignore +11 -0
- data/site_template/.prettierrc +6 -0
- data/site_template/.rspec +1 -0
- data/site_template/.rubocop.yml +8 -0
- data/site_template/.ruby-version +1 -0
- data/site_template/Dockerfile +47 -0
- data/site_template/Gemfile +33 -0
- data/site_template/Procfile.dev +3 -0
- data/site_template/README.md +98 -0
- data/site_template/Rakefile +18 -0
- data/site_template/app/views/errors/not_found.rb +14 -0
- data/site_template/app/views/errors/service_error.rb +14 -0
- data/site_template/app/views/layouts/error.rb +13 -0
- data/site_template/app/views/layouts/head.rb +60 -0
- data/site_template/app/views/layouts/site.rb +37 -0
- data/site_template/app/views/pages/home.rb +22 -0
- data/site_template/app/views/ui/.keep +0 -0
- data/site_template/app/views/ui/footer.rb +21 -0
- data/site_template/app/views/ui/navbar.rb +19 -0
- data/site_template/bin/console +11 -0
- data/site_template/bin/dev +12 -0
- data/site_template/bin/lint +18 -0
- data/site_template/bin/staticky +28 -0
- data/site_template/config/boot.rb +15 -0
- data/site_template/config/routes.rb +9 -0
- data/site_template/config/site.erb +12 -0
- data/site_template/config/vite.json +17 -0
- data/site_template/config.ru +8 -0
- data/site_template/content/.keep +0 -0
- data/site_template/content/demo.md +12 -0
- data/site_template/frontend/controllers/.keep +0 -0
- data/site_template/frontend/controllers/index.js +3 -0
- data/site_template/frontend/entrypoints/application.js +8 -0
- data/site_template/frontend/images/.keep +0 -0
- data/site_template/frontend/images/hero.jpg +0 -0
- data/site_template/frontend/stylesheets/application.css +61 -0
- data/site_template/frontend/stylesheets/syntax.css +151 -0
- data/site_template/frontend/tailwindcss/variable_font_plugin.js +103 -0
- data/site_template/frontend/turbo_transitions.js +54 -0
- data/site_template/lib/component.rb +32 -0
- data/site_template/lib/icon.rb +39 -0
- data/site_template/lib/layout.rb +4 -0
- data/site_template/lib/page.rb +11 -0
- data/site_template/lib/vite_helpers.rb +38 -0
- data/site_template/logs/.keep +0 -0
- data/site_template/nginx.conf +59 -0
- data/site_template/package.json +31 -0
- data/site_template/postcss.config.js +6 -0
- data/site_template/public/android-chrome-192x192.png +0 -0
- data/site_template/public/android-chrome-512x512.png +0 -0
- data/site_template/public/apple-touch-icon.png +0 -0
- data/site_template/public/favicon-16x16.png +0 -0
- data/site_template/public/favicon-32x32.png +0 -0
- data/site_template/public/favicon.ico +0 -0
- data/site_template/public/robots.txt.erb +1 -0
- data/site_template/public/site.webmanifest.erb +19 -0
- data/site_template/public/sitemap.xml +0 -0
- data/site_template/spec/spec_helper.rb +30 -0
- data/site_template/spec/views/pages/home_spec.rb +9 -0
- data/site_template/tailwind.config.js +83 -0
- data/site_template/vite.config.ts +14 -0
- metadata +273 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 41fa452e7c74ade67ffd58486af13cb4b121eda6cd51ffd5dc25105981cea0c2
|
4
|
+
data.tar.gz: c8c2a3a15bc2cf0225d7763a825e8c1a34af2a0762777091e0c6c581723d485f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff6e8e759028df62d4ce1a9304c2b8caa7fd38c5684e9a653445d14a46ffd4b880fd3c05bb393890d055262db28d37384c47af02ff72c1ce37dafaed33842d33
|
7
|
+
data.tar.gz: 3aa63ca27344d93d99a92463e85f4cd75a547360da6682f6727cbd022b258c540df7dd39b4631f15f08f8f19bcb08bc21aedfe07736aab3267092d4d1db5ec9d
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Nolan J Tait
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Staticky
|
2
|
+
|
3
|
+
Staticky is a static site builder for Ruby maximalists. I built this library
|
4
|
+
because I wanted something more scriptable than Bridgetown and Jekyll that had
|
5
|
+
first-class support for Phlex components.
|
6
|
+
|
7
|
+
[Phlex](https://phlex.fun) makes building component based frontends fun and
|
8
|
+
I wanted to extend the developer experience of something like Rails but focused
|
9
|
+
on static sites.
|
10
|
+
|
11
|
+
I am using this at https://taintedcoders.com. (soon)
|
12
|
+
|
13
|
+
- Hot reloading in development with Roda serving static files
|
14
|
+
- Docker deployment with NGINX
|
15
|
+
|
16
|
+
You can find a working setup in `site_template` folder.
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Install the gem and add to the application's Gemfile by executing:
|
21
|
+
|
22
|
+
$ bundle add staticky
|
23
|
+
|
24
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
25
|
+
|
26
|
+
$ gem install staticky
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
First you can use the CLI to generate a new template:
|
31
|
+
|
32
|
+
```
|
33
|
+
staticky new my_blog --url "https://example.com"
|
34
|
+
```
|
35
|
+
|
36
|
+
This will generate a new site at `./my_blog`, install your dependencies and run
|
37
|
+
`rspec` just to make sure everything got set up correctly.
|
38
|
+
|
39
|
+
Once your site is generated you can use the router to define how your content
|
40
|
+
maps to routes in `config/routes.rb`:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
Staticky.router.define do
|
44
|
+
root to: Pages::Home
|
45
|
+
match "404", to: Errors::NotFound
|
46
|
+
match "500", to: Errors::ServiceError
|
47
|
+
|
48
|
+
# Write your own logic to parse your data into components
|
49
|
+
Site.posts.each_value do |model|
|
50
|
+
match model.relative_url, to: Posts::Show.new(model)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Each route takes a Phlex component (or any object that outputs a string from
|
56
|
+
`#call`). We can either pass the class for a default initialization (we just
|
57
|
+
call `.new`) or initialize it ourselves.
|
58
|
+
|
59
|
+
Here is an example of what the `Posts::Show` component might look like. We are
|
60
|
+
using a [protos](https://github.com/inhouse-work/protos) component, but you can
|
61
|
+
use plain old Phlex components if you like.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
module Posts
|
65
|
+
class Show < Protos::Component
|
66
|
+
param :post, reader: false
|
67
|
+
|
68
|
+
def around_template(&)
|
69
|
+
render Layouts::Post.new(class: css[:layout], &)
|
70
|
+
end
|
71
|
+
|
72
|
+
def view_template
|
73
|
+
render Posts::Header.new(@post)
|
74
|
+
render Posts::Outline.new(@post, class: css[:outline])
|
75
|
+
render Posts::Markdown.new(@post, class: css[:post])
|
76
|
+
render Posts::Footer.new(@post)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def theme
|
82
|
+
{
|
83
|
+
layout: "mr-0 lg:mr-[--sidebar-width] md:ml-[--sidebar-width]",
|
84
|
+
outline: %w[
|
85
|
+
my-md
|
86
|
+
md:fixed
|
87
|
+
md:left-0
|
88
|
+
md:top-[--navbar-height]
|
89
|
+
md:pl-[--viewport-padding]
|
90
|
+
md:w-[--sidebar-width]
|
91
|
+
],
|
92
|
+
post: "prose mx-auto pb-lg"
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
When you are developing your site you run `bin/dev` to start your development
|
100
|
+
server on http://localhost:9292. This will automatically reload after a short
|
101
|
+
period when you make changes.
|
102
|
+
|
103
|
+
Assets are handled by Vite by default, but you can have whatever build process
|
104
|
+
you like just by tweaking `Procfile.dev` and your `Rakefile`. You will also need
|
105
|
+
to create your own view helpers for linking your assets.
|
106
|
+
|
107
|
+
By default, to build your site you run the builder, usually inside a Rakefile:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
require "vite_ruby"
|
111
|
+
|
112
|
+
ViteRuby.install_tasks
|
113
|
+
|
114
|
+
desc "Precompile assets"
|
115
|
+
task :environment do
|
116
|
+
require "./config/boot"
|
117
|
+
end
|
118
|
+
|
119
|
+
namespace :site do
|
120
|
+
desc "Precompile assets"
|
121
|
+
task build: :environment do
|
122
|
+
Rake::Task["vite:build"].invoke
|
123
|
+
Staticky.builder.call
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
This will output your site to `./build` by default.
|
129
|
+
|
130
|
+
## Configuration
|
131
|
+
|
132
|
+
We can override the configuration according to the settings defined on the main
|
133
|
+
module:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Staticky.configure do |config|
|
137
|
+
config.env = :test
|
138
|
+
config.build_path = Pathname.new("dist")
|
139
|
+
config.root_path = Pathname(__dir__)
|
140
|
+
config.logger = Logger.new($stdout)
|
141
|
+
config.server_logger = Logger.new($stdout)
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
## Development
|
146
|
+
|
147
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
148
|
+
`bin/rspec` to run the tests. You can also run `bin/console` for an interactive
|
149
|
+
prompt that will allow you to experiment.
|
150
|
+
|
151
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
152
|
+
release a new version, update the version number in `version.rb`, and then run
|
153
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
154
|
+
git commits and the created tag, and push the `.gem` file to
|
155
|
+
[rubygems.org](https://rubygems.org).
|
156
|
+
|
157
|
+
## Contributing
|
158
|
+
|
159
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nolantait/staticky.
|
160
|
+
|
161
|
+
## License
|
162
|
+
|
163
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/staticky
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Builder
|
5
|
+
include Dry::Events::Publisher[:builder]
|
6
|
+
include Deps[:files, :router]
|
7
|
+
|
8
|
+
register_event("started")
|
9
|
+
register_event("finished")
|
10
|
+
register_event("before_resource")
|
11
|
+
register_event("after_resource")
|
12
|
+
|
13
|
+
def self.call(...) = new(...).call
|
14
|
+
|
15
|
+
def on(event_type, &block) = subscribe(event_type, &block)
|
16
|
+
|
17
|
+
def call
|
18
|
+
publish("started")
|
19
|
+
copy_public_files
|
20
|
+
build_site
|
21
|
+
publish("finished")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_site
|
27
|
+
@router
|
28
|
+
.resources
|
29
|
+
.each do |resource|
|
30
|
+
publish("before_resource", resource:)
|
31
|
+
compile output_path(resource.filepath), resource.build
|
32
|
+
publish("after_resource", resource:)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def copy_public_files
|
37
|
+
public_folder = Staticky.root_path.join("public")
|
38
|
+
return unless @files.exist? public_folder
|
39
|
+
|
40
|
+
@files.children(public_folder).each do |file|
|
41
|
+
# file => "favicon.ico"
|
42
|
+
copy(public_path(file), output_path(file))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def compile(destination, content)
|
47
|
+
@files.write destination, content
|
48
|
+
end
|
49
|
+
|
50
|
+
def copy(source, destination)
|
51
|
+
@files.cp source, destination
|
52
|
+
end
|
53
|
+
|
54
|
+
def public_path(path)
|
55
|
+
Staticky.root_path.join("public", path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def output_path(path)
|
59
|
+
Staticky.build_path.join(path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/staticky/cli.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/cli"
|
4
|
+
|
5
|
+
module Staticky
|
6
|
+
module CLI
|
7
|
+
module Commands
|
8
|
+
extend Dry::CLI::Registry
|
9
|
+
|
10
|
+
class Version < Dry::CLI::Command
|
11
|
+
desc "Print version"
|
12
|
+
|
13
|
+
def call(*) = puts VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
class Build < Dry::CLI::Command
|
17
|
+
desc "Build site"
|
18
|
+
|
19
|
+
def call(*) = Staticky.builder.call
|
20
|
+
end
|
21
|
+
|
22
|
+
class Generate < Dry::CLI::Command
|
23
|
+
desc "Create new site"
|
24
|
+
|
25
|
+
argument :path,
|
26
|
+
required: true,
|
27
|
+
desc: "Relative path where the site will be generated"
|
28
|
+
|
29
|
+
option :url,
|
30
|
+
default: "https://example.com",
|
31
|
+
desc: "Site URL",
|
32
|
+
aliases: ["-u"]
|
33
|
+
option :title,
|
34
|
+
default: "Example",
|
35
|
+
desc: "Site title",
|
36
|
+
aliases: ["-t"]
|
37
|
+
option :description,
|
38
|
+
default: "Example site",
|
39
|
+
desc: "Site description",
|
40
|
+
aliases: ["-d"]
|
41
|
+
option :twitter,
|
42
|
+
default: "",
|
43
|
+
desc: "Twitter handle",
|
44
|
+
aliases: ["-t"]
|
45
|
+
|
46
|
+
def call(path:, **)
|
47
|
+
path = Pathname.new(path).expand_path
|
48
|
+
|
49
|
+
Staticky.generator.call(path, **)
|
50
|
+
|
51
|
+
commands = [
|
52
|
+
"bundle install",
|
53
|
+
"bundle binstubs bundler rake rspec-core vite_ruby",
|
54
|
+
"yarn install",
|
55
|
+
"bin/rspec"
|
56
|
+
].join(" && ")
|
57
|
+
|
58
|
+
system(commands, chdir: path) || abort("install failed")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
register "version", Version
|
63
|
+
register "build", Build
|
64
|
+
register "new", Generate
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.new(...)
|
68
|
+
Dry::CLI.new(Commands)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Container < Dry::System::Container
|
5
|
+
use :env
|
6
|
+
use :zeitwerk
|
7
|
+
use :monitoring
|
8
|
+
|
9
|
+
configure do |config|
|
10
|
+
config.root = Pathname(__dir__).join("..").join("..")
|
11
|
+
config.inflector = Dry::Inflector.new do |inflections|
|
12
|
+
inflections.acronym("CLI")
|
13
|
+
end
|
14
|
+
config.component_dirs.add "lib" do |dir|
|
15
|
+
dir.add_to_load_path = false
|
16
|
+
dir.auto_register = false
|
17
|
+
dir.namespaces.add "staticky", key: nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
register(:files, Staticky::Filesystem.real)
|
22
|
+
register(:router, Staticky::Router.new)
|
23
|
+
register(:builder, Staticky::Builder.new)
|
24
|
+
register(:generator, Staticky::Generator.new)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "staticky-files"
|
5
|
+
|
6
|
+
module Staticky
|
7
|
+
class Filesystem < SimpleDelegator
|
8
|
+
def self.test
|
9
|
+
files = Staticky::Files.new(memory: true)
|
10
|
+
new(files)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.real
|
14
|
+
files = Staticky::Files.new
|
15
|
+
new(files)
|
16
|
+
end
|
17
|
+
|
18
|
+
def touch(*files)
|
19
|
+
files.each do |file|
|
20
|
+
super(file)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def children(directory)
|
25
|
+
tokens = [".", ".."]
|
26
|
+
entries(directory).reject { |entry| tokens.include?(entry) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Generator
|
5
|
+
include Deps[:files]
|
6
|
+
|
7
|
+
class ViewContext
|
8
|
+
attr_reader :title, :description, :twitter, :url
|
9
|
+
|
10
|
+
def initialize(url:, title: "", description: "", twitter: "")
|
11
|
+
@title = title
|
12
|
+
@description = description
|
13
|
+
@twitter = twitter
|
14
|
+
@url = URI(url)
|
15
|
+
|
16
|
+
raise ArgumentError, "Must be a full url: #{@url}" unless @url.host
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(**kwargs)
|
21
|
+
super
|
22
|
+
@path = GEM_ROOT.join("site_template")
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(output_dir, **)
|
26
|
+
view_context = ViewContext.new(**)
|
27
|
+
output_dir = Pathname.new(output_dir).expand_path
|
28
|
+
|
29
|
+
Pathname.glob(@path.join("**/*"), File::FNM_DOTMATCH).each do |file|
|
30
|
+
build_file(file:, output_dir:, view_context:)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_file(file:, output_dir:, view_context:)
|
37
|
+
return if file.directory?
|
38
|
+
|
39
|
+
relative_path = file.relative_path_from(@path)
|
40
|
+
target = output_dir.join(relative_path)
|
41
|
+
|
42
|
+
# This handles files like:
|
43
|
+
# - index.html.erb -> index.html
|
44
|
+
# - site.erb -> site.rb
|
45
|
+
if target.extname == ".erb"
|
46
|
+
target = target.sub_ext("")
|
47
|
+
target = target.sub_ext(".rb") if target.extname == ""
|
48
|
+
end
|
49
|
+
|
50
|
+
build_template(file:, target:, view_context:)
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_template(file:, target:, view_context:)
|
54
|
+
files.write(target, render_template(file, view_context))
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_template(file, view_context)
|
58
|
+
return file.read unless file.extname == ".erb"
|
59
|
+
|
60
|
+
Tilt::ERBTemplate.new(file).render(view_context)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Phlex
|
5
|
+
module ViewHelpers
|
6
|
+
def link_to(text, href, **, &block) # rubocop:disable Metrics/ParameterLists
|
7
|
+
block ||= proc { text }
|
8
|
+
href = Staticky.router.resolve(href).url
|
9
|
+
|
10
|
+
a(href:, **, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Phlex::SGML.prepend Staticky::Phlex::ViewHelpers
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
Resource = Data.define(:url, :component) do
|
5
|
+
def full_filepath
|
6
|
+
Staticky.build_path.join(filepath)
|
7
|
+
end
|
8
|
+
|
9
|
+
def read
|
10
|
+
full_filepath.read
|
11
|
+
end
|
12
|
+
|
13
|
+
def filepath
|
14
|
+
root? ? "index.html" : "#{url}.html"
|
15
|
+
end
|
16
|
+
|
17
|
+
def root?
|
18
|
+
url == "/"
|
19
|
+
end
|
20
|
+
|
21
|
+
def build(view_context: ViewContext.new(self))
|
22
|
+
component.call(view_context:)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Router
|
5
|
+
class Definition
|
6
|
+
attr_reader :resources
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@routes_by_path = {}
|
10
|
+
@routes_by_component = {}
|
11
|
+
@resources = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def match(path, to:)
|
15
|
+
component = to.then do |object|
|
16
|
+
object.is_a?(Class) ? object.new : object
|
17
|
+
end
|
18
|
+
|
19
|
+
@resources << resource = Resource.new(url: path, component:)
|
20
|
+
@routes_by_path[path] = resource
|
21
|
+
@routes_by_component[component.class] = resource
|
22
|
+
end
|
23
|
+
|
24
|
+
def root(to:)
|
25
|
+
match("/", to:)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve(path)
|
29
|
+
@routes_by_path.fetch(path) { @routes_by_component.fetch(path) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(path)
|
33
|
+
@routes.delete(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def filepaths
|
37
|
+
@resources.map { |resource| rename_key(resource.url) }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def rename_key(key)
|
43
|
+
return "index.html" if key == "/"
|
44
|
+
|
45
|
+
"#{key}.html"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
class Router
|
5
|
+
# DOCS: Holds routes as a class instance variable. This class is expected to
|
6
|
+
# be used as a singleton by requiring the "routes.rb" file.
|
7
|
+
#
|
8
|
+
# NOTE: Why do we need our own router? Why not just use Roda for these
|
9
|
+
# definitions? Roda is a routing tree and cannot be introspected easily.
|
10
|
+
# In Staticky when we build we need to do a lot of introspection to link
|
11
|
+
# routes to resources on the file system.
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@definition = Staticky::Router::Definition.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def define(&block)
|
18
|
+
tap do
|
19
|
+
@definition.instance_eval(&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def filepaths
|
24
|
+
@definition.filepaths
|
25
|
+
end
|
26
|
+
|
27
|
+
def resources
|
28
|
+
@definition.resources
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve(path)
|
32
|
+
@definition.resolve(path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "roda"
|
4
|
+
|
5
|
+
require_relative "../staticky"
|
6
|
+
|
7
|
+
module Staticky
|
8
|
+
class Server < Roda
|
9
|
+
# This runs a local development server that serves the static files
|
10
|
+
# Require this in your config.ru file and run something like `rackup` to
|
11
|
+
# start the server
|
12
|
+
|
13
|
+
NotFound = Class.new(Staticky::Error)
|
14
|
+
|
15
|
+
plugin :common_logger, Staticky.server_logger, method: :debug
|
16
|
+
plugin :render, engine: "html"
|
17
|
+
|
18
|
+
plugin :not_found do
|
19
|
+
raise NotFound if Staticky.env.test?
|
20
|
+
|
21
|
+
Staticky.build_path.join("404.html").read
|
22
|
+
end
|
23
|
+
|
24
|
+
plugin :error_handler do |e|
|
25
|
+
raise e if Staticky.env.test?
|
26
|
+
|
27
|
+
Staticky.build_path.join("500.html").read
|
28
|
+
end
|
29
|
+
|
30
|
+
route do |r|
|
31
|
+
Staticky.resources.each do |resource|
|
32
|
+
case resource.filepath
|
33
|
+
when "index.html"
|
34
|
+
r.root do
|
35
|
+
render(inline: resource.read)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
r.get resource.url do
|
39
|
+
render(inline: resource.read)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Need to return nil or Roda is unhappy
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|