statique 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/.overcommit.yml +54 -0
- data/.rubocop.yml +21 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Dockerfile +20 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +88 -0
- data/Rakefile +41 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/statique +20 -0
- data/lib/statique/app.rb +86 -0
- data/lib/statique/cli/build.rb +76 -0
- data/lib/statique/cli/init.rb +50 -0
- data/lib/statique/cli/server.rb +67 -0
- data/lib/statique/cli.rb +54 -0
- data/lib/statique/discover.rb +87 -0
- data/lib/statique/document.rb +63 -0
- data/lib/statique/mode.rb +40 -0
- data/lib/statique/paginator.rb +55 -0
- data/lib/statique/version.rb +5 -0
- data/lib/statique.rb +68 -0
- data/lib/templates/index.md +5 -0
- data/lib/templates/layout.slim +13 -0
- data/sig/statique/mode.rbs +16 -0
- data/sig/statique/version.rbs +3 -0
- data/sig/statique.rbs +4 -0
- data/statique.gemspec +53 -0
- metadata +315 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Statique
|
4
|
+
class CLI
|
5
|
+
class Server
|
6
|
+
class LoggerWrapper
|
7
|
+
def <<(msg)
|
8
|
+
method, uri, status, time, content_type = msg.chomp.split(":")
|
9
|
+
log(:info, "#{method} #{uri}", status: status.to_i, time: time.to_f, content_type:)
|
10
|
+
end
|
11
|
+
|
12
|
+
def log(type, msg, attrs = {})
|
13
|
+
Statique.ui.public_send(type, msg, attrs.merge(app: "webrick"))
|
14
|
+
end
|
15
|
+
|
16
|
+
%i[unknown fatal error warn info debug].each do |type|
|
17
|
+
define_method(type) do |msg|
|
18
|
+
log(type, msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method(:"#{type}?") do
|
22
|
+
Statique.ui.compare_levels(Statique.ui.class.config.level, type.to_sym) != :lt
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(port: 3000)
|
28
|
+
@port = port
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
Statique.ui.info "Starting server", port: @port
|
33
|
+
|
34
|
+
logger = LoggerWrapper.new
|
35
|
+
|
36
|
+
Rack::Handler::WEBrick.run(Statique::App.freeze,
|
37
|
+
Port: @port,
|
38
|
+
Host: "localhost",
|
39
|
+
Logger: logger,
|
40
|
+
AccessLog: [[logger, "%m:%U:%s:%T:%{Content-Type}o"]])
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop
|
44
|
+
Statique.ui.info "Stopping server"
|
45
|
+
Rack::Handler::WEBrick.shutdown
|
46
|
+
end
|
47
|
+
|
48
|
+
class EventStream
|
49
|
+
def initialize(type)
|
50
|
+
@type = type
|
51
|
+
end
|
52
|
+
|
53
|
+
def puts(message)
|
54
|
+
Statique.ui.public_send(@type, message)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(message)
|
58
|
+
puts(message)
|
59
|
+
end
|
60
|
+
|
61
|
+
def sync
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/statique/cli.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
class Statique
|
6
|
+
class CLI < Thor
|
7
|
+
autoload :Server, "statique/cli/server"
|
8
|
+
autoload :Build, "statique/cli/build"
|
9
|
+
autoload :Init, "statique/cli/init"
|
10
|
+
|
11
|
+
package_name "Statique"
|
12
|
+
|
13
|
+
COMMAND_ALIASES = {
|
14
|
+
"version" => %w[-v --version]
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.exit_on_failure?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.aliases_for(command_name)
|
26
|
+
COMMAND_ALIASES.select { |k, _| k == command_name }.invert
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "init", "Initialize new Statique website"
|
30
|
+
argument :name, optional: true, desc: "Name of the directory to initialise the Statique website in"
|
31
|
+
def init
|
32
|
+
Init.new(name).run
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "server", "Start Statique server"
|
36
|
+
def server
|
37
|
+
Statique.mode.server!
|
38
|
+
Server.new.run
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "build", "Build Statique site"
|
42
|
+
def build
|
43
|
+
Statique.mode.build!
|
44
|
+
Build.new(options.dup).run
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "version", "Prints the statique's version information"
|
48
|
+
def version
|
49
|
+
Statique.ui.info "Statique v#{Statique::VERSION}"
|
50
|
+
end
|
51
|
+
|
52
|
+
map aliases_for("version")
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Statique
|
4
|
+
class Discover
|
5
|
+
attr_reader :documents, :collections, :files
|
6
|
+
|
7
|
+
SUPPORTED_EXTENSIONS = %w[
|
8
|
+
slim
|
9
|
+
md
|
10
|
+
builder
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
GLOB = "**/*.{#{SUPPORTED_EXTENSIONS.join(",")}}"
|
14
|
+
|
15
|
+
def initialize(root)
|
16
|
+
@root = root
|
17
|
+
@documents = []
|
18
|
+
@collections = Hashie::Mash.new { |hash, key| hash[key] = Set.new }
|
19
|
+
|
20
|
+
discover_files!
|
21
|
+
discover!
|
22
|
+
|
23
|
+
Statique.mode.build do
|
24
|
+
@files.freeze
|
25
|
+
@documents.freeze
|
26
|
+
@collections.freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
watch_for_changes if Statique.mode.server?
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def discover_files!
|
35
|
+
@files = @root.glob(GLOB)
|
36
|
+
ensure
|
37
|
+
Statique.ui.debug "Discovered files", count: @files.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def discover!
|
41
|
+
@files.each do |file|
|
42
|
+
process(file)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def process(file)
|
47
|
+
document = Document.new(file)
|
48
|
+
|
49
|
+
documents << document
|
50
|
+
|
51
|
+
Array(document.meta.collection).each do |collection|
|
52
|
+
collections[collection] << document
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def watch_for_changes
|
57
|
+
require "filewatcher"
|
58
|
+
|
59
|
+
@filewatcher = Filewatcher.new([Statique.paths.content, Statique.paths.layouts])
|
60
|
+
@filewatcher_thread = Thread.new(@filewatcher) do |watcher|
|
61
|
+
watcher.watch do |file, event|
|
62
|
+
Statique.ui.debug "File change event", file: file, event: event
|
63
|
+
discover_files!
|
64
|
+
path = Pathname.new(file)
|
65
|
+
remove_file!(path)
|
66
|
+
process(path) unless event == :deleted
|
67
|
+
end
|
68
|
+
end
|
69
|
+
Statique.ui.debug "Started file watcher", filewatcher: @filewatcher, thread: @filewatcher_thread
|
70
|
+
|
71
|
+
at_exit do
|
72
|
+
Statique.ui.debug "Closing file watcher", thread: @filewatcher_thread
|
73
|
+
@filewatcher.stop
|
74
|
+
@filewatcher.finalize
|
75
|
+
@filewatcher_thread.join
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def remove_file!(path)
|
80
|
+
documents.delete_if { _1.file == path }
|
81
|
+
collections.each_value do |collection|
|
82
|
+
# TODO: See if set can index by some particular property to avoid looping
|
83
|
+
collection.delete_if { _1.file == path }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hashie"
|
4
|
+
|
5
|
+
class Statique
|
6
|
+
class Document
|
7
|
+
attr_reader :file, :meta, :content
|
8
|
+
|
9
|
+
def initialize(file)
|
10
|
+
parsed = FrontMatterParser::Parser.parse_file(file)
|
11
|
+
@file, @meta, @content = file.freeze, Hashie::Mash.new(parsed.front_matter).freeze, parsed.content.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
case basename
|
16
|
+
when "index.slim" then "/"
|
17
|
+
when "index.md" then "/"
|
18
|
+
else
|
19
|
+
"/#{meta.permalink || basename.delete_suffix(extname).delete_prefix(Statique.paths.content.to_s)}"
|
20
|
+
end.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def view_name
|
24
|
+
basename.delete_suffix(extname).freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def engine_name
|
28
|
+
extname.delete_prefix(".").freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def layout_name
|
32
|
+
meta.fetch("layout") { "layout" }.freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
def title
|
36
|
+
meta.title.freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
content
|
41
|
+
end
|
42
|
+
|
43
|
+
def pagination_pages
|
44
|
+
return unless Statique.discover.collections.key?(meta.paginates)
|
45
|
+
collection = Statique.discover.collections[meta.paginates]
|
46
|
+
(collection.size.to_f / Pagy::DEFAULT[:items]).ceil
|
47
|
+
end
|
48
|
+
|
49
|
+
def published_at
|
50
|
+
@published_at ||= file.ctime.freeze
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def basename
|
56
|
+
@basename ||= file.basename.to_s.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
def extname
|
60
|
+
@extname ||= file.extname.to_s.freeze
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Statique
|
4
|
+
class Mode
|
5
|
+
MODE_BUILD = "build"
|
6
|
+
MODE_SERVER = "server"
|
7
|
+
MODES = [MODE_BUILD, MODE_SERVER].freeze
|
8
|
+
|
9
|
+
def initialize(mode = MODE_SERVER)
|
10
|
+
raise ArgumentError, "Mode can't be empty" if mode.nil? || mode.empty?
|
11
|
+
raise ArgumentError, "Mode must be one of #{MODES}" unless MODES.include?(mode.to_s)
|
12
|
+
|
13
|
+
@mode = mode == MODE_SERVER ? MODE_SERVER : MODE_BUILD
|
14
|
+
end
|
15
|
+
|
16
|
+
def server!
|
17
|
+
@mode = MODE_SERVER
|
18
|
+
end
|
19
|
+
|
20
|
+
def build!
|
21
|
+
@mode = MODE_BUILD
|
22
|
+
end
|
23
|
+
|
24
|
+
def server?
|
25
|
+
@mode == MODE_SERVER
|
26
|
+
end
|
27
|
+
|
28
|
+
def build?
|
29
|
+
@mode == MODE_BUILD
|
30
|
+
end
|
31
|
+
|
32
|
+
def server
|
33
|
+
yield if server?
|
34
|
+
end
|
35
|
+
|
36
|
+
def build
|
37
|
+
yield if build?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Statique
|
4
|
+
class Paginator
|
5
|
+
attr_reader :documents
|
6
|
+
|
7
|
+
def initialize(pagy, documents, path)
|
8
|
+
@pagy, @documents, @path = pagy, documents, path
|
9
|
+
end
|
10
|
+
|
11
|
+
def page
|
12
|
+
@pagy.page
|
13
|
+
end
|
14
|
+
|
15
|
+
def total_pages
|
16
|
+
@pagy.pages
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_documents
|
20
|
+
@pagy.count
|
21
|
+
end
|
22
|
+
|
23
|
+
def previous_page
|
24
|
+
@pagy.prev
|
25
|
+
end
|
26
|
+
|
27
|
+
def previous_page_path
|
28
|
+
page_path(@pagy.prev)
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_page
|
32
|
+
@pagy.next
|
33
|
+
end
|
34
|
+
|
35
|
+
def next_page_path
|
36
|
+
page_path(@pagy.next)
|
37
|
+
end
|
38
|
+
|
39
|
+
def per_page
|
40
|
+
@pagy.items
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def page_path(page)
|
46
|
+
return unless page
|
47
|
+
|
48
|
+
if page == 1
|
49
|
+
Statique.url(File.join(@path))
|
50
|
+
else
|
51
|
+
Statique.url(File.join(@path, "page", page.to_s))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/statique.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "front_matter_parser"
|
4
|
+
require "hashie"
|
5
|
+
require "pathname"
|
6
|
+
require "pagy"
|
7
|
+
require "rack"
|
8
|
+
require "tty-logger"
|
9
|
+
require "dry-configurable"
|
10
|
+
|
11
|
+
::FrontMatterParser::SyntaxParser::Builder = FrontMatterParser::SyntaxParser::MultiLineComment["=begin", "=end"]
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.expand_path("..", __FILE__))
|
14
|
+
require "zeitwerk"
|
15
|
+
|
16
|
+
loader = Zeitwerk::Loader.for_gem
|
17
|
+
loader.inflector.inflect(
|
18
|
+
"cli" => "CLI"
|
19
|
+
)
|
20
|
+
loader.setup
|
21
|
+
|
22
|
+
class Statique
|
23
|
+
extend Dry::Configurable
|
24
|
+
|
25
|
+
class Error < StandardError; end
|
26
|
+
|
27
|
+
setting :paths, reader: true do
|
28
|
+
setting :pwd, default: Pathname.pwd, constructor: -> { Pathname(_1) }
|
29
|
+
setting :public, default: Pathname.pwd.join("public"), constructor: -> { Statique.pwd.join(_1) }
|
30
|
+
setting :content, default: Pathname.pwd.join("content"), constructor: -> { Statique.pwd.join(_1) }
|
31
|
+
setting :layouts, default: Pathname.pwd.join("layouts"), constructor: -> { Statique.pwd.join(_1) }
|
32
|
+
setting :assets, default: Pathname.pwd.join("assets"), constructor: -> { Statique.pwd.join(_1) }
|
33
|
+
setting :destination, default: Pathname.pwd.join("dist"), constructor: -> { Statique.pwd.join(_1) }
|
34
|
+
end
|
35
|
+
setting :root_url, default: "/", reader: true
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def discover
|
39
|
+
@discover ||= Discover.new(paths.content)
|
40
|
+
end
|
41
|
+
|
42
|
+
def mode
|
43
|
+
@mode ||= Mode.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def pwd
|
47
|
+
@pwd ||= Pathname.pwd.freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
def version
|
51
|
+
VERSION
|
52
|
+
end
|
53
|
+
|
54
|
+
def ui
|
55
|
+
@ui ||= TTY::Logger.new(output: $stdout) do |config|
|
56
|
+
config.level = :debug if ENV["DEBUG"] == "true"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def url(document_or_path)
|
61
|
+
File.join(root_url, document_or_path.is_a?(Document) ? document_or_path.path : document_or_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_queue
|
65
|
+
@build_queue ||= Queue.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
meta charset="UTF-8"
|
5
|
+
meta name="generator" content="Statique #{Statique.version}"
|
6
|
+
title Statique Website
|
7
|
+
== assets(:css)
|
8
|
+
== assets(:js)
|
9
|
+
body
|
10
|
+
header
|
11
|
+
h1 Statique Website
|
12
|
+
main== yield
|
13
|
+
footer Made with Statique v#{Statique.version}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Statique
|
2
|
+
class Mode
|
3
|
+
MODE_BUILD: "build"
|
4
|
+
MODE_SERVER: "server"
|
5
|
+
MODES: ["build", "server"]
|
6
|
+
@mode: "build" | "server"
|
7
|
+
|
8
|
+
def initialize: (?mode: "build" | "server") -> void
|
9
|
+
def server!: -> "server"
|
10
|
+
def build!: -> "build"
|
11
|
+
def server?: -> bool
|
12
|
+
def build?: -> bool
|
13
|
+
def server: -> nil
|
14
|
+
def build: -> nil
|
15
|
+
end
|
16
|
+
end
|
data/sig/statique.rbs
ADDED
data/statique.gemspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/statique/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "statique"
|
7
|
+
spec.version = Statique::VERSION
|
8
|
+
spec.authors = ["Piotr Usewicz"]
|
9
|
+
spec.email = ["piotr@layer22.com"]
|
10
|
+
|
11
|
+
spec.summary = "Static website generator"
|
12
|
+
spec.description = "Statique is a static website generator written in Ruby using Roda"
|
13
|
+
spec.homepage = "https://github.com/pusewicz/statique"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/pusewicz/statique"
|
19
|
+
spec.metadata["changelog_uri"] = "https://raw.githubusercontent.com/pusewicz/statique/v#{spec.version}/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
spec.add_dependency "builder", "~> 3.2.4"
|
34
|
+
spec.add_dependency "commonmarker", "~> 0.23.2"
|
35
|
+
spec.add_dependency "dry-configurable", "~> 0.14.0"
|
36
|
+
spec.add_dependency "filewatcher", "~> 1.1.1"
|
37
|
+
spec.add_dependency "front_matter_parser", "~> 1.0.1"
|
38
|
+
spec.add_dependency "hashie", "~> 5.0.0"
|
39
|
+
spec.add_dependency "memo_wise", "~> 1.6.0"
|
40
|
+
spec.add_dependency "pagy", "~> 5.9.3"
|
41
|
+
spec.add_dependency "rack-rewrite", "~> 1.5.1"
|
42
|
+
spec.add_dependency "roda", "~> 3.52"
|
43
|
+
spec.add_dependency "sassc", "~> 2.4.0"
|
44
|
+
spec.add_dependency "slim", "~> 4.1.0"
|
45
|
+
spec.add_dependency "thor", "~> 1.2.1"
|
46
|
+
spec.add_dependency "tilt", "~> 2.0.10"
|
47
|
+
spec.add_dependency "tty-logger", "~> 0.6.0"
|
48
|
+
spec.add_dependency "webrick", "~> 1.7.0"
|
49
|
+
spec.add_dependency "zeitwerk", "~> 2.5.4"
|
50
|
+
|
51
|
+
# For more information and examples about making a new gem, check out our
|
52
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
53
|
+
end
|