staticky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +11 -0
  4. data/CHANGELOG.md +5 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +163 -0
  7. data/Rakefile +12 -0
  8. data/bin/staticky +7 -0
  9. data/lib/staticky/builder.rb +62 -0
  10. data/lib/staticky/cli.rb +71 -0
  11. data/lib/staticky/container.rb +26 -0
  12. data/lib/staticky/deps.rb +5 -0
  13. data/lib/staticky/environment.rb +8 -0
  14. data/lib/staticky/error.rb +5 -0
  15. data/lib/staticky/filesystem.rb +29 -0
  16. data/lib/staticky/generator.rb +63 -0
  17. data/lib/staticky/phlex/view_helpers.rb +16 -0
  18. data/lib/staticky/resource.rb +25 -0
  19. data/lib/staticky/router/definition.rb +49 -0
  20. data/lib/staticky/router.rb +35 -0
  21. data/lib/staticky/server.rb +48 -0
  22. data/lib/staticky/version.rb +5 -0
  23. data/lib/staticky/view_context.rb +17 -0
  24. data/lib/staticky.rb +51 -0
  25. data/site_template/.dockerignore +4 -0
  26. data/site_template/.gitignore +11 -0
  27. data/site_template/.prettierrc +6 -0
  28. data/site_template/.rspec +1 -0
  29. data/site_template/.rubocop.yml +8 -0
  30. data/site_template/.ruby-version +1 -0
  31. data/site_template/Dockerfile +47 -0
  32. data/site_template/Gemfile +33 -0
  33. data/site_template/Procfile.dev +3 -0
  34. data/site_template/README.md +98 -0
  35. data/site_template/Rakefile +18 -0
  36. data/site_template/app/views/errors/not_found.rb +14 -0
  37. data/site_template/app/views/errors/service_error.rb +14 -0
  38. data/site_template/app/views/layouts/error.rb +13 -0
  39. data/site_template/app/views/layouts/head.rb +60 -0
  40. data/site_template/app/views/layouts/site.rb +37 -0
  41. data/site_template/app/views/pages/home.rb +22 -0
  42. data/site_template/app/views/ui/.keep +0 -0
  43. data/site_template/app/views/ui/footer.rb +21 -0
  44. data/site_template/app/views/ui/navbar.rb +19 -0
  45. data/site_template/bin/console +11 -0
  46. data/site_template/bin/dev +12 -0
  47. data/site_template/bin/lint +18 -0
  48. data/site_template/bin/staticky +28 -0
  49. data/site_template/config/boot.rb +15 -0
  50. data/site_template/config/routes.rb +9 -0
  51. data/site_template/config/site.erb +12 -0
  52. data/site_template/config/vite.json +17 -0
  53. data/site_template/config.ru +8 -0
  54. data/site_template/content/.keep +0 -0
  55. data/site_template/content/demo.md +12 -0
  56. data/site_template/frontend/controllers/.keep +0 -0
  57. data/site_template/frontend/controllers/index.js +3 -0
  58. data/site_template/frontend/entrypoints/application.js +8 -0
  59. data/site_template/frontend/images/.keep +0 -0
  60. data/site_template/frontend/images/hero.jpg +0 -0
  61. data/site_template/frontend/stylesheets/application.css +61 -0
  62. data/site_template/frontend/stylesheets/syntax.css +151 -0
  63. data/site_template/frontend/tailwindcss/variable_font_plugin.js +103 -0
  64. data/site_template/frontend/turbo_transitions.js +54 -0
  65. data/site_template/lib/component.rb +32 -0
  66. data/site_template/lib/icon.rb +39 -0
  67. data/site_template/lib/layout.rb +4 -0
  68. data/site_template/lib/page.rb +11 -0
  69. data/site_template/lib/vite_helpers.rb +38 -0
  70. data/site_template/logs/.keep +0 -0
  71. data/site_template/nginx.conf +59 -0
  72. data/site_template/package.json +31 -0
  73. data/site_template/postcss.config.js +6 -0
  74. data/site_template/public/android-chrome-192x192.png +0 -0
  75. data/site_template/public/android-chrome-512x512.png +0 -0
  76. data/site_template/public/apple-touch-icon.png +0 -0
  77. data/site_template/public/favicon-16x16.png +0 -0
  78. data/site_template/public/favicon-32x32.png +0 -0
  79. data/site_template/public/favicon.ico +0 -0
  80. data/site_template/public/robots.txt.erb +1 -0
  81. data/site_template/public/site.webmanifest.erb +19 -0
  82. data/site_template/public/sitemap.xml +0 -0
  83. data/site_template/spec/spec_helper.rb +30 -0
  84. data/site_template/spec/views/pages/home_spec.rb +9 -0
  85. data/site_template/tailwind.config.js +83 -0
  86. data/site_template/vite.config.ts +14 -0
  87. 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ require:
2
+ - rubocop-inhouse
3
+ - rubocop-capybara
4
+
5
+ inherit_gem:
6
+ rubocop-inhouse:
7
+ - config/default.yml
8
+ - config/capybara.yml
9
+
10
+ AllCops:
11
+ TargetRubyVersion: 3.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-08
4
+
5
+ - Initial release
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/staticky ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "staticky"
6
+
7
+ Staticky::CLI.new.call
@@ -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
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ Deps = Container.injector
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ Environment = Data.define(:name) do
5
+ def development? = name == :development
6
+ def test? = name == :test
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ class Error < StandardError; end
5
+ 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