ruby-md-ssg 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/AGENTS.md +17 -0
- data/CONTEXT_LOG.md +6 -0
- data/README.md +17 -0
- data/exe/ruby_md_ssg +9 -0
- data/lib/ruby_md_ssg/cli.rb +251 -0
- data/lib/ruby_md_ssg/compiler.rb +115 -0
- data/lib/ruby_md_ssg/document.rb +100 -0
- data/lib/ruby_md_ssg/document_finder.rb +24 -0
- data/lib/ruby_md_ssg/html_formatter.rb +20 -0
- data/lib/ruby_md_ssg/layout_renderer.rb +33 -0
- data/lib/ruby_md_ssg/markdown_renderer.rb +11 -0
- data/lib/ruby_md_ssg/menu_config.rb +71 -0
- data/lib/ruby_md_ssg/menu_generator.rb +87 -0
- data/lib/ruby_md_ssg/paths.rb +46 -0
- data/lib/ruby_md_ssg/server.rb +120 -0
- data/lib/ruby_md_ssg/version.rb +5 -0
- data/lib/ruby_md_ssg.rb +14 -0
- data/template/site/Gemfile.erb +12 -0
- data/template/site/README.md.erb +16 -0
- data/template/site/Rakefile +24 -0
- data/template/site/assets/app.js +12 -0
- data/template/site/assets/style.css +106 -0
- data/template/site/bin/build +6 -0
- data/template/site/bin/compile +6 -0
- data/template/site/bin/generate_menu +6 -0
- data/template/site/bin/serve +6 -0
- data/template/site/bin/test +8 -0
- data/template/site/docs/blog/launch.md +11 -0
- data/template/site/docs/guides/setup.md +7 -0
- data/template/site/docs/guides/workflow.md +7 -0
- data/template/site/docs/ideas/government.md +5 -0
- data/template/site/docs/index.md.erb +8 -0
- data/template/site/templates/layout.html.erb +42 -0
- metadata +122 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative 'paths'
|
|
6
|
+
require_relative 'document_finder'
|
|
7
|
+
require_relative 'menu_config'
|
|
8
|
+
|
|
9
|
+
module RubyMdSsg
|
|
10
|
+
class MenuGenerator
|
|
11
|
+
def initialize(docs_dir: Paths.docs_dir, menu_path: Paths.menu_config)
|
|
12
|
+
@docs_dir = docs_dir
|
|
13
|
+
@menu_path = menu_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate
|
|
17
|
+
documents = DocumentFinder.new(docs_dir: docs_dir).all
|
|
18
|
+
default_menu = MenuConfig.from_documents(documents)
|
|
19
|
+
config = if File.exist?(menu_path)
|
|
20
|
+
merge_existing(MenuConfig.load(menu_path), default_menu)
|
|
21
|
+
else
|
|
22
|
+
default_menu
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
serialized = YAML.dump(config.to_h)
|
|
26
|
+
FileUtils.mkdir_p(File.dirname(menu_path))
|
|
27
|
+
existing = File.exist?(menu_path) ? File.read(menu_path) : nil
|
|
28
|
+
File.write(menu_path, serialized) unless existing == serialized
|
|
29
|
+
menu_path
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :docs_dir, :menu_path
|
|
35
|
+
|
|
36
|
+
def merge_existing(existing, default_menu)
|
|
37
|
+
default_lookup = {}
|
|
38
|
+
default_menu.sections.each do |section|
|
|
39
|
+
default_lookup[normalized(section.title)] = section
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
merged_sections = existing.sections.map do |existing_section|
|
|
43
|
+
key = normalized(existing_section.title)
|
|
44
|
+
default_section = default_lookup.delete(key)
|
|
45
|
+
if default_section
|
|
46
|
+
SectionMerger.merge(existing_section, default_section)
|
|
47
|
+
else
|
|
48
|
+
existing_section
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
additional_sections = default_lookup.values.sort_by(&:title)
|
|
53
|
+
MenuConfig.new(merged_sections + additional_sections)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def normalized(value)
|
|
57
|
+
value.to_s.downcase
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module SectionMerger
|
|
61
|
+
module_function
|
|
62
|
+
|
|
63
|
+
def merge(existing_section, default_section)
|
|
64
|
+
existing_links = {}
|
|
65
|
+
existing_section.links.each do |link|
|
|
66
|
+
existing_links[normalized(link.route)] = link
|
|
67
|
+
end
|
|
68
|
+
default_section.links.each do |link|
|
|
69
|
+
key = normalized(link.route)
|
|
70
|
+
existing_links[key] ||= link
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
merged_links = existing_section.links.map { |link| existing_links[normalized(link.route)] }
|
|
74
|
+
additional_links = existing_links.values.reject { |link| merged_links.include?(link) }
|
|
75
|
+
merged_links += additional_links
|
|
76
|
+
RubyMdSsg::MenuConfig::Section.new(
|
|
77
|
+
title: existing_section.title,
|
|
78
|
+
links: merged_links
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def normalized(value)
|
|
83
|
+
value.to_s.downcase
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyMdSsg
|
|
4
|
+
module Paths
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def root
|
|
8
|
+
@root ||= begin
|
|
9
|
+
env_root = ENV.fetch('RUBY_MD_SSG_ROOT', nil)
|
|
10
|
+
if env_root && !env_root.empty?
|
|
11
|
+
File.expand_path(env_root)
|
|
12
|
+
else
|
|
13
|
+
Dir.pwd
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def root=(value)
|
|
19
|
+
@root = value && File.expand_path(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset!
|
|
23
|
+
@root = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def docs_dir
|
|
27
|
+
File.join(root, 'docs')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build_dir
|
|
31
|
+
File.join(root, 'build')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def assets_dir
|
|
35
|
+
File.join(root, 'assets')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def menu_config
|
|
39
|
+
File.join(docs_dir, 'menu.yml')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def templates_dir
|
|
43
|
+
File.join(root, 'templates')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'webrick'
|
|
4
|
+
require 'digest'
|
|
5
|
+
|
|
6
|
+
module RubyMdSsg
|
|
7
|
+
class Server
|
|
8
|
+
class << self
|
|
9
|
+
def start(options = {})
|
|
10
|
+
new(default_options.merge(options)).start
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def default_options
|
|
16
|
+
{
|
|
17
|
+
port: 4000,
|
|
18
|
+
docs: Paths.docs_dir,
|
|
19
|
+
build: Paths.build_dir,
|
|
20
|
+
assets: Paths.assets_dir,
|
|
21
|
+
menu: Paths.menu_config,
|
|
22
|
+
auto_build: true,
|
|
23
|
+
watch: true,
|
|
24
|
+
interval: 1.0,
|
|
25
|
+
base_url: ENV.fetch('RUBY_MD_SSG_BASE_URL', nil)
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(options)
|
|
31
|
+
@options = options
|
|
32
|
+
@server = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def start
|
|
36
|
+
build_site if options[:auto_build]
|
|
37
|
+
watch_for_changes if options[:watch]
|
|
38
|
+
start_server
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :options, :server
|
|
44
|
+
|
|
45
|
+
def build_site
|
|
46
|
+
generator = MenuGenerator.new(docs_dir: options[:docs], menu_path: options[:menu])
|
|
47
|
+
generator.generate
|
|
48
|
+
|
|
49
|
+
compiler = Compiler.new(
|
|
50
|
+
docs_dir: options[:docs],
|
|
51
|
+
build_dir: options[:build],
|
|
52
|
+
assets_dir: options[:assets],
|
|
53
|
+
menu_path: options[:menu],
|
|
54
|
+
base_url: options[:base_url]
|
|
55
|
+
)
|
|
56
|
+
compiler.compile
|
|
57
|
+
puts "[#{timestamp}] Build complete."
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
warn "[build] #{e.class}: #{e.message}"
|
|
60
|
+
e.backtrace&.take(5)&.each { |line| warn " #{line}" }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def watch_for_changes
|
|
64
|
+
Thread.new do
|
|
65
|
+
last = fingerprint
|
|
66
|
+
loop do
|
|
67
|
+
sleep options[:interval]
|
|
68
|
+
current = fingerprint
|
|
69
|
+
next if current == last
|
|
70
|
+
|
|
71
|
+
puts '[watch] Change detected. Rebuilding...'
|
|
72
|
+
build_site
|
|
73
|
+
last = current
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
warn "[watch] #{e.class}: #{e.message}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def start_server
|
|
81
|
+
@server = WEBrick::HTTPServer.new(
|
|
82
|
+
Port: options[:port],
|
|
83
|
+
DocumentRoot: options[:build],
|
|
84
|
+
AccessLog: [],
|
|
85
|
+
Logger: WEBrick::Log.new($stderr, WEBrick::Log::INFO)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
trap('INT') { server.shutdown }
|
|
89
|
+
|
|
90
|
+
puts "Serving #{options[:build]} at http://localhost:#{options[:port]}"
|
|
91
|
+
server.start
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def fingerprint
|
|
95
|
+
files = watched_files
|
|
96
|
+
return 'empty' if files.empty?
|
|
97
|
+
|
|
98
|
+
Digest::SHA1.hexdigest(
|
|
99
|
+
files.sort.flat_map do |path|
|
|
100
|
+
[path, File.mtime(path).to_f.to_s, (File.size?(path) || 0).to_s]
|
|
101
|
+
end.join('|')
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def watched_files
|
|
106
|
+
patterns = [
|
|
107
|
+
File.join(options[:docs], '**', '*.md'),
|
|
108
|
+
File.join(options[:docs], '*.yml'),
|
|
109
|
+
File.join(options[:assets], '**', '*'),
|
|
110
|
+
File.join(Paths.root, 'templates', '**', '*.erb')
|
|
111
|
+
]
|
|
112
|
+
patterns.flat_map { |pattern| Dir.glob(pattern, File::FNM_DOTMATCH) }
|
|
113
|
+
.select { |path| File.file?(path) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def timestamp
|
|
117
|
+
Time.now.strftime('%H:%M:%S')
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
data/lib/ruby_md_ssg.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ruby_md_ssg/version'
|
|
4
|
+
require_relative 'ruby_md_ssg/paths'
|
|
5
|
+
require_relative 'ruby_md_ssg/document'
|
|
6
|
+
require_relative 'ruby_md_ssg/document_finder'
|
|
7
|
+
require_relative 'ruby_md_ssg/menu_config'
|
|
8
|
+
require_relative 'ruby_md_ssg/menu_generator'
|
|
9
|
+
require_relative 'ruby_md_ssg/markdown_renderer'
|
|
10
|
+
require_relative 'ruby_md_ssg/layout_renderer'
|
|
11
|
+
require_relative 'ruby_md_ssg/html_formatter'
|
|
12
|
+
require_relative 'ruby_md_ssg/compiler'
|
|
13
|
+
require_relative 'ruby_md_ssg/server'
|
|
14
|
+
require_relative 'ruby_md_ssg/cli'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# <%= project_name %>
|
|
2
|
+
|
|
3
|
+
Generated by Ruby MD SSG <%= gem_version %>.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `bundle exec ruby_md_ssg build` — regenerate the site into `build/` (writes `sitemap.xml`)
|
|
8
|
+
- `bundle exec ruby_md_ssg serve` — serve `build/` with automatic rebuilds
|
|
9
|
+
- `bundle exec ruby_md_ssg menu` — refresh `docs/menu.yml`
|
|
10
|
+
- `bundle exec ruby_md_ssg test` — run the Minitest suite (if present)
|
|
11
|
+
|
|
12
|
+
Set `RUBY_MD_SSG_BASE_URL` (or pass `--base-url`) when building to control sitemap URLs.
|
|
13
|
+
|
|
14
|
+
Docs live in `docs/`, assets in `assets/`, templates in `templates/`.
|
|
15
|
+
|
|
16
|
+
GitHub Pages deployment is configured via `.github/workflows/deploy.yml`; adjust the workflow or remove it if you handle deployments differently.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rake/testtask'
|
|
2
|
+
|
|
3
|
+
desc 'Run test suite'
|
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
|
5
|
+
t.libs << 'test'
|
|
6
|
+
t.pattern = 'test/**/*_test.rb'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc 'Run RuboCop'
|
|
10
|
+
task :lint do
|
|
11
|
+
sh 'bundle exec rubocop'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'Build the static site'
|
|
15
|
+
task :build do
|
|
16
|
+
sh 'bundle exec ruby_md_ssg build'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Serve the static site'
|
|
20
|
+
task :serve do
|
|
21
|
+
sh 'bundle exec ruby_md_ssg serve'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task default: :test
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Base script for Ruby MD SSG
|
|
2
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
3
|
+
const currentPath = window.location.pathname.replace(/\/$/, '') || '/';
|
|
4
|
+
document
|
|
5
|
+
.querySelectorAll('.layout__menu a')
|
|
6
|
+
.forEach((link) => {
|
|
7
|
+
const linkPath = link.getAttribute('href').replace(/\/$/, '') || '/';
|
|
8
|
+
if (linkPath === currentPath) {
|
|
9
|
+
link.classList.add('is-active');
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Base layout for Ruby MD SSG */
|
|
2
|
+
* {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
9
|
+
background: #f8f9fb;
|
|
10
|
+
color: #111;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
a {
|
|
14
|
+
color: #0366d6;
|
|
15
|
+
text-decoration: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
a:hover {
|
|
19
|
+
text-decoration: underline;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.layout {
|
|
23
|
+
display: grid;
|
|
24
|
+
grid-template-columns: 320px 1fr;
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.layout__sidebar {
|
|
29
|
+
background: #111827;
|
|
30
|
+
color: #f8f9fb;
|
|
31
|
+
padding: 2rem;
|
|
32
|
+
position: sticky;
|
|
33
|
+
top: 0;
|
|
34
|
+
align-self: start;
|
|
35
|
+
height: 100vh;
|
|
36
|
+
overflow-y: auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.layout__header h1 {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: 1.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.layout__header p {
|
|
45
|
+
margin: 0.5rem 0 1.5rem;
|
|
46
|
+
color: rgba(248, 249, 251, 0.7);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.layout__menu {
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 1.5rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.menu-section h2 {
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: 0.875rem;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
letter-spacing: 0.08em;
|
|
59
|
+
color: rgba(248, 249, 251, 0.6);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.menu-section ul {
|
|
63
|
+
list-style: none;
|
|
64
|
+
padding: 0;
|
|
65
|
+
margin: 0.75rem 0 0;
|
|
66
|
+
display: grid;
|
|
67
|
+
gap: 0.4rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.menu-section a {
|
|
71
|
+
color: #f8f9fb;
|
|
72
|
+
display: block;
|
|
73
|
+
padding: 0.25rem 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.menu-section a.is-active {
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
color: #38bdf8;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.layout__content {
|
|
82
|
+
padding: 3rem;
|
|
83
|
+
background: white;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.layout__content article {
|
|
87
|
+
max-width: 720px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.layout__content h1 {
|
|
91
|
+
font-size: 2.25rem;
|
|
92
|
+
margin-top: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.layout__content p {
|
|
96
|
+
line-height: 1.6;
|
|
97
|
+
font-size: 1rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.layout__content pre {
|
|
101
|
+
background: #1f2937;
|
|
102
|
+
color: #f8f9fb;
|
|
103
|
+
padding: 1rem;
|
|
104
|
+
border-radius: 6px;
|
|
105
|
+
overflow: auto;
|
|
106
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Ruby MD SSG Launch Notes
|
|
2
|
+
|
|
3
|
+
Welcome to the first release of Ruby MD SSG. The goal is to build static sites with a tiny toolchain.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Full-site rebuild in a single command.
|
|
8
|
+
- Navigation driven by human-editable YAML.
|
|
9
|
+
- Zero JavaScript build tooling required.
|
|
10
|
+
|
|
11
|
+
Tell us what features you need next by opening an issue.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
1. Install Ruby 3.2 or newer.
|
|
4
|
+
2. Run `bundle install` inside the repository.
|
|
5
|
+
3. Execute `bundle exec ruby_md_ssg build` to generate the static site. This also emits `build/sitemap.xml`; set `RUBY_MD_SSG_BASE_URL` or pass `--base-url` to control URLs.
|
|
6
|
+
|
|
7
|
+
You can now open the files in the `build/` directory or run the server command to preview locally.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Recommended Workflow
|
|
2
|
+
|
|
3
|
+
- Write new content in `docs/` using markdown headings to outline the story.
|
|
4
|
+
- Regenerate the menu with `bundle exec ruby_md_ssg menu` so the navigation picks up new pages.
|
|
5
|
+
- Rebuild the site with `bundle exec ruby_md_ssg build` and review the output (including `build/sitemap.xml`) before opening a pull request.
|
|
6
|
+
|
|
7
|
+
Keep each page focused on a single topic and prefer relative links when referencing peer documents.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: <%= helpers.human_project_name %>
|
|
3
|
+
description: Site generated with Ruby MD SSG <%= gem_version %>.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Welcome to <%= helpers.human_project_name %>
|
|
7
|
+
|
|
8
|
+
Ruby MD SSG converts markdown content into a static site with a shared layout. Update this file to change the landing page.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title><%= title %> · Ruby MD SSG</title>
|
|
7
|
+
<% if meta_description && !meta_description.empty? %>
|
|
8
|
+
<meta name="description" content="<%= meta_description %>" />
|
|
9
|
+
<% end %>
|
|
10
|
+
<link rel="stylesheet" href="/assets/style.css" />
|
|
11
|
+
<script defer src="/assets/app.js"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div class="layout">
|
|
15
|
+
<aside class="layout__sidebar">
|
|
16
|
+
<header class="layout__header">
|
|
17
|
+
<h1>Ruby MD SSG</h1>
|
|
18
|
+
<p>Markdown to static site with minimal tooling.</p>
|
|
19
|
+
</header>
|
|
20
|
+
<nav class="layout__menu">
|
|
21
|
+
<% menu.sections.each do |section| %>
|
|
22
|
+
<section class="menu-section">
|
|
23
|
+
<h2><%= section.title %></h2>
|
|
24
|
+
<ul>
|
|
25
|
+
<% section.links.each do |link| %>
|
|
26
|
+
<li>
|
|
27
|
+
<a href="<%= link.route %>"><%= link.label %></a>
|
|
28
|
+
</li>
|
|
29
|
+
<% end %>
|
|
30
|
+
</ul>
|
|
31
|
+
</section>
|
|
32
|
+
<% end %>
|
|
33
|
+
</nav>
|
|
34
|
+
</aside>
|
|
35
|
+
<main class="layout__content">
|
|
36
|
+
<article>
|
|
37
|
+
<%= body_html %>
|
|
38
|
+
</article>
|
|
39
|
+
</main>
|
|
40
|
+
</div>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|