statique 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|