ottogen 0.1.0 → 1.0.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 +4 -4
- data/README.md +59 -43
- data/bin/otto +1 -0
- data/lib/ottogen/collection.rb +35 -0
- data/lib/ottogen/collection_item.rb +61 -0
- data/lib/ottogen/config.rb +126 -0
- data/lib/ottogen/front_matter.rb +30 -0
- data/lib/ottogen/layout.rb +65 -0
- data/lib/ottogen/otto.rb +36 -15
- data/lib/ottogen/ottogen.rb +168 -28
- data/lib/ottogen/page.rb +58 -0
- data/lib/ottogen/permalink.rb +35 -0
- data/lib/ottogen/post.rb +108 -0
- data/lib/ottogen/scaffold.rb +50 -0
- data/spec/ottogen/collection_spec.rb +61 -0
- data/spec/ottogen/config_spec.rb +249 -0
- data/spec/ottogen/layout_spec.rb +150 -0
- data/spec/ottogen/ottogen_spec.rb +430 -5
- data/spec/ottogen/page_spec.rb +101 -0
- data/spec/ottogen/permalink_spec.rb +49 -0
- data/spec/ottogen/post_spec.rb +172 -0
- data/spec/spec_helper.rb +51 -0
- metadata +176 -57
data/lib/ottogen/ottogen.rb
CHANGED
|
@@ -1,51 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'asciidoctor'
|
|
4
|
+
require 'date'
|
|
2
5
|
require 'fileutils'
|
|
3
6
|
require 'listen'
|
|
7
|
+
require 'webrick'
|
|
8
|
+
|
|
9
|
+
require_relative 'collection_item'
|
|
10
|
+
require_relative 'config'
|
|
11
|
+
require_relative 'layout'
|
|
12
|
+
require_relative 'page'
|
|
13
|
+
require_relative 'permalink'
|
|
14
|
+
require_relative 'post'
|
|
15
|
+
require_relative 'scaffold'
|
|
4
16
|
|
|
5
17
|
module Ottogen
|
|
6
18
|
class Ottogen
|
|
7
|
-
BUILD_DIR = '_build'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
26
|
-
|
|
19
|
+
BUILD_DIR = '_build'
|
|
20
|
+
|
|
21
|
+
def self.init(dir)
|
|
22
|
+
puts '✨ Initializing static site...'
|
|
23
|
+
if !dir.nil? && Dir.exist?(dir)
|
|
24
|
+
puts '❌ Error: Directory already exists'
|
|
25
|
+
exit(1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if dir.nil?
|
|
29
|
+
files_in_current_dir = Dir.glob('**/*')
|
|
30
|
+
unless files_in_current_dir.empty?
|
|
31
|
+
puts '❌ Error: Directory must be empty'
|
|
32
|
+
exit(1)
|
|
33
|
+
end
|
|
34
|
+
init_in_current_dir
|
|
35
|
+
else
|
|
36
|
+
init_with_dir(dir)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts '✅'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.build(drafts: false)
|
|
43
|
+
puts '🔨 Building static site...'
|
|
44
|
+
error_if_not_otto_project
|
|
45
|
+
config = load_config(drafts: drafts)
|
|
46
|
+
FileUtils.mkdir_p(BUILD_DIR)
|
|
47
|
+
FileUtils.cp_r 'assets/', "#{BUILD_DIR}/assets"
|
|
48
|
+
documents_for(config).each { |doc| convert_document(doc, config) }
|
|
49
|
+
puts '✅'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.documents_for(config)
|
|
53
|
+
collection_items = config.collections.values.flat_map { |c| c.output? ? c.items : [] }
|
|
54
|
+
load_pages + config.posts + collection_items
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.load_pages
|
|
58
|
+
Dir.glob('pages/**/*.adoc').map { |path| Page.read(path) }
|
|
59
|
+
rescue Page::Error => e
|
|
60
|
+
puts "❌ Error: #{e.message}"
|
|
61
|
+
exit(1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.convert_document(doc, config)
|
|
65
|
+
apply_permalink(doc, config)
|
|
66
|
+
html = render_document(doc, config)
|
|
67
|
+
write_output(doc.output_path(BUILD_DIR), html)
|
|
68
|
+
rescue Layout::Error => e
|
|
69
|
+
puts "❌ Error in #{doc.path}: #{e.message}"
|
|
70
|
+
exit(1)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.apply_permalink(doc, config)
|
|
74
|
+
template = doc.front_matter['permalink']
|
|
75
|
+
template ||= config['permalink'] if doc.is_a?(Post)
|
|
76
|
+
return if template.nil?
|
|
77
|
+
|
|
78
|
+
doc.permalink = Permalink.expand(template, doc)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.render_document(doc, config)
|
|
82
|
+
layout_name = doc.front_matter['layout']
|
|
83
|
+
attributes = config.asciidoctor_attributes.merge(doc.asciidoctor_attributes)
|
|
84
|
+
body = Asciidoctor.convert(doc.body,
|
|
27
85
|
safe: :safe,
|
|
28
|
-
|
|
29
|
-
|
|
86
|
+
standalone: layout_name.nil?,
|
|
87
|
+
attributes: attributes)
|
|
88
|
+
return body unless layout_name
|
|
89
|
+
|
|
90
|
+
Layout.find(layout_name).render(content: body, site: config, page: doc)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.write_output(path, html)
|
|
94
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
95
|
+
File.write(path, html)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.generate(page)
|
|
99
|
+
puts '📝 Generating a new page...'
|
|
100
|
+
error_if_not_otto_project
|
|
101
|
+
page_title = page.split('-').map(&:capitalize).join(' ')
|
|
102
|
+
File.write("pages/#{page}.adoc", "= #{page_title}\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.new_post(title)
|
|
106
|
+
error_if_not_otto_project
|
|
107
|
+
FileUtils.mkdir_p('_posts')
|
|
108
|
+
slug = Permalink.slugify(title)
|
|
109
|
+
date = Date.today
|
|
110
|
+
path = "_posts/#{date.iso8601}-#{slug}.adoc"
|
|
111
|
+
File.write(path, "---\ntitle: #{title}\ndate: #{date.iso8601}\n---\n\n")
|
|
112
|
+
puts "📝 Created #{path}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.doctor
|
|
116
|
+
problems = collect_problems
|
|
117
|
+
if problems.empty?
|
|
118
|
+
puts '✅ All checks passed'
|
|
119
|
+
else
|
|
120
|
+
puts '❌ Problems:'
|
|
121
|
+
problems.each { |p| puts " - #{p}" }
|
|
122
|
+
exit(1)
|
|
30
123
|
end
|
|
31
|
-
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.collect_problems
|
|
127
|
+
problems = []
|
|
128
|
+
problems << '.otto marker missing (run `otto init` first)' unless File.exist?('.otto')
|
|
129
|
+
problems << 'config.yml missing' unless File.exist?('config.yml')
|
|
130
|
+
problems << 'pages/ directory missing' unless Dir.exist?('pages')
|
|
131
|
+
problems
|
|
32
132
|
end
|
|
33
133
|
|
|
34
134
|
def self.clean
|
|
35
|
-
puts
|
|
135
|
+
puts '🧽 Cleaning build directory...'
|
|
136
|
+
error_if_not_otto_project
|
|
36
137
|
return unless Dir.exist?(BUILD_DIR)
|
|
138
|
+
|
|
37
139
|
FileUtils.rmtree(BUILD_DIR)
|
|
38
|
-
puts
|
|
140
|
+
puts '✅'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.serve
|
|
144
|
+
puts '🤖 Starting server...'
|
|
145
|
+
error_if_not_otto_project
|
|
146
|
+
root = File.expand_path("#{Dir.pwd}/#{Ottogen::BUILD_DIR}")
|
|
147
|
+
server = WEBrick::HTTPServer.new Port: 8778, DocumentRoot: root
|
|
148
|
+
trap 'INT' do
|
|
149
|
+
server.shutdown
|
|
150
|
+
end
|
|
151
|
+
server.start
|
|
152
|
+
rescue Errno::EADDRINUSE
|
|
153
|
+
puts '❌ Server port already in use'
|
|
154
|
+
exit(1)
|
|
39
155
|
end
|
|
40
156
|
|
|
41
|
-
def self.watch
|
|
42
|
-
puts
|
|
157
|
+
def self.watch(drafts: false)
|
|
158
|
+
puts '👀 Watching files...'
|
|
159
|
+
error_if_not_otto_project
|
|
43
160
|
listener = Listen.to(Dir.pwd, ignore: [/_build/]) do |modified, added, removed|
|
|
44
161
|
puts(modified: modified, added: added, removed: removed)
|
|
45
|
-
build
|
|
162
|
+
build(drafts: drafts)
|
|
46
163
|
end
|
|
47
164
|
listener.start
|
|
48
165
|
sleep
|
|
49
166
|
end
|
|
167
|
+
|
|
168
|
+
def self.init_with_dir(dir)
|
|
169
|
+
Dir.mkdir(dir)
|
|
170
|
+
Scaffold.write(dir)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def self.init_in_current_dir
|
|
174
|
+
Scaffold.write('.')
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.error_if_not_otto_project
|
|
178
|
+
return if File.exist?('.otto')
|
|
179
|
+
|
|
180
|
+
puts '❌ Error: Current directory is not an otto project'
|
|
181
|
+
exit(1)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.load_config(drafts: false)
|
|
185
|
+
Config.load(drafts: drafts)
|
|
186
|
+
rescue Config::Error => e
|
|
187
|
+
puts "❌ Error: #{e.message}"
|
|
188
|
+
exit(1)
|
|
189
|
+
end
|
|
50
190
|
end
|
|
51
191
|
end
|
data/lib/ottogen/page.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'front_matter'
|
|
4
|
+
|
|
5
|
+
module Ottogen
|
|
6
|
+
class Page
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
def self.read(path)
|
|
10
|
+
raw = File.read(path)
|
|
11
|
+
front_matter, body = FrontMatter.split(raw, path)
|
|
12
|
+
new(path: path, front_matter: front_matter, body: body)
|
|
13
|
+
rescue FrontMatter::Error => e
|
|
14
|
+
raise Error, e.message
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :path, :front_matter, :body
|
|
18
|
+
attr_accessor :permalink
|
|
19
|
+
|
|
20
|
+
def initialize(front_matter:, body:, path: nil)
|
|
21
|
+
@path = path
|
|
22
|
+
@front_matter = front_matter
|
|
23
|
+
@body = body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def url
|
|
27
|
+
return @permalink.url if @permalink
|
|
28
|
+
return nil unless @path
|
|
29
|
+
|
|
30
|
+
"/#{@path.sub(%r{^pages/}, '').sub(/\.adoc\z/, '.html')}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def output_path(build_dir)
|
|
34
|
+
return @permalink.output_path(build_dir) if @permalink
|
|
35
|
+
|
|
36
|
+
relative = @path.sub(%r{^pages/}, '').sub(/\.adoc\z/, '.html')
|
|
37
|
+
File.join(build_dir, relative)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def asciidoctor_attributes
|
|
41
|
+
attrs = @front_matter.transform_keys { |key| "page_#{key}" }
|
|
42
|
+
page_url = url
|
|
43
|
+
attrs['page_url'] = page_url if page_url
|
|
44
|
+
attrs
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def respond_to_missing?(name, include_private = false)
|
|
48
|
+
@front_matter.key?(name.to_s) || super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def method_missing(name, *args)
|
|
52
|
+
key = name.to_s
|
|
53
|
+
return @front_matter[key] if @front_matter.key?(key)
|
|
54
|
+
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ottogen
|
|
4
|
+
class Permalink
|
|
5
|
+
TOKEN_PATTERN = /:(year|month|day|slug|title)/
|
|
6
|
+
|
|
7
|
+
TOKEN_RESOLVERS = {
|
|
8
|
+
':year' => ->(doc) { doc.respond_to?(:date) && doc.date ? doc.date.strftime('%Y') : '' },
|
|
9
|
+
':month' => ->(doc) { doc.respond_to?(:date) && doc.date ? doc.date.strftime('%m') : '' },
|
|
10
|
+
':day' => ->(doc) { doc.respond_to?(:date) && doc.date ? doc.date.strftime('%d') : '' },
|
|
11
|
+
':slug' => ->(doc) { doc.respond_to?(:slug) ? doc.slug.to_s : '' },
|
|
12
|
+
':title' => ->(doc) { doc.respond_to?(:title) ? slugify(doc.title) : '' }
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def self.expand(template, doc)
|
|
16
|
+
expanded = template.gsub(TOKEN_PATTERN) { |token| TOKEN_RESOLVERS.fetch(token).call(doc) }
|
|
17
|
+
new(expanded)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.slugify(str)
|
|
21
|
+
str.to_s.downcase.gsub(/[^a-z0-9]+/, '-').sub(/\A-/, '').sub(/-\z/, '')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :path
|
|
25
|
+
alias url path
|
|
26
|
+
|
|
27
|
+
def initialize(path)
|
|
28
|
+
@path = path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def output_path(build_dir)
|
|
32
|
+
File.join(build_dir, @path.end_with?('/') ? "#{@path}index.html" : @path)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/ottogen/post.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
require_relative 'front_matter'
|
|
6
|
+
|
|
7
|
+
module Ottogen
|
|
8
|
+
class Post
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
POSTS_DIR = '_posts'
|
|
12
|
+
DRAFTS_DIR = '_drafts'
|
|
13
|
+
FILENAME_PATTERN = /\A(\d{4})-(\d{2})-(\d{2})-(.+)\.adoc\z/
|
|
14
|
+
|
|
15
|
+
def self.read(path)
|
|
16
|
+
date, slug = parse_filename(path)
|
|
17
|
+
front_matter, body = FrontMatter.split(File.read(path), path)
|
|
18
|
+
new(path: path, date: date, slug: slug, front_matter: front_matter, body: body)
|
|
19
|
+
rescue FrontMatter::Error => e
|
|
20
|
+
raise Error, e.message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.read_draft(path)
|
|
24
|
+
slug = File.basename(path, '.adoc')
|
|
25
|
+
front_matter, body = FrontMatter.split(File.read(path), path)
|
|
26
|
+
new(path: path, date: Date.today, slug: slug, front_matter: front_matter, body: body)
|
|
27
|
+
rescue FrontMatter::Error => e
|
|
28
|
+
raise Error, e.message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.parse_filename(path)
|
|
32
|
+
match = FILENAME_PATTERN.match(File.basename(path))
|
|
33
|
+
raise Error, "post filename must match YYYY-MM-DD-slug.adoc: #{path}" unless match
|
|
34
|
+
|
|
35
|
+
year, month, day, slug = match.captures
|
|
36
|
+
[Date.new(year.to_i, month.to_i, day.to_i), slug]
|
|
37
|
+
end
|
|
38
|
+
private_class_method :parse_filename
|
|
39
|
+
|
|
40
|
+
def self.discover(dir = POSTS_DIR)
|
|
41
|
+
return [] unless Dir.exist?(dir)
|
|
42
|
+
|
|
43
|
+
Dir.glob(File.join(dir, '*.adoc')).map { |path| read(path) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.discover_drafts(dir = DRAFTS_DIR)
|
|
47
|
+
return [] unless Dir.exist?(dir)
|
|
48
|
+
|
|
49
|
+
Dir.glob(File.join(dir, '*.adoc')).map { |path| read_draft(path) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
attr_reader :path, :date, :slug, :front_matter, :body
|
|
53
|
+
attr_accessor :permalink
|
|
54
|
+
|
|
55
|
+
def initialize(path:, date:, slug:, front_matter:, body:)
|
|
56
|
+
@path = path
|
|
57
|
+
@date = date
|
|
58
|
+
@slug = slug
|
|
59
|
+
@front_matter = front_matter
|
|
60
|
+
@body = body
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def title
|
|
64
|
+
@front_matter['title'] || slug.split('-').map(&:capitalize).join(' ')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def tags
|
|
68
|
+
Array(@front_matter['tags'])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def categories
|
|
72
|
+
Array(@front_matter['categories'])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def url
|
|
76
|
+
return @permalink.url if @permalink
|
|
77
|
+
|
|
78
|
+
"/#{slug}.html"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def output_path(build_dir)
|
|
82
|
+
return @permalink.output_path(build_dir) if @permalink
|
|
83
|
+
|
|
84
|
+
File.join(build_dir, "#{slug}.html")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def asciidoctor_attributes
|
|
88
|
+
attrs = @front_matter.transform_keys { |key| "page_#{key}" }
|
|
89
|
+
attrs['page_title'] ||= title
|
|
90
|
+
attrs['page_date'] = date.iso8601
|
|
91
|
+
attrs['page_slug'] = slug
|
|
92
|
+
attrs['page_url'] = url
|
|
93
|
+
attrs.delete('page_permalink')
|
|
94
|
+
attrs
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def respond_to_missing?(name, include_private = false)
|
|
98
|
+
name == :title || name == :url || @front_matter.key?(name.to_s) || super
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def method_missing(name, *args)
|
|
102
|
+
key = name.to_s
|
|
103
|
+
return @front_matter[key] if @front_matter.key?(key)
|
|
104
|
+
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Ottogen
|
|
6
|
+
module Scaffold
|
|
7
|
+
CONFIG = <<~YAML
|
|
8
|
+
title: "Otto site"
|
|
9
|
+
description: ""
|
|
10
|
+
url: ""
|
|
11
|
+
baseurl: ""
|
|
12
|
+
YAML
|
|
13
|
+
|
|
14
|
+
WELCOME = <<~ADOC
|
|
15
|
+
---
|
|
16
|
+
layout: default
|
|
17
|
+
title: Welcome
|
|
18
|
+
---
|
|
19
|
+
= Welcome to Otto!
|
|
20
|
+
|
|
21
|
+
Otto is a static site generator that uses AsciiDoc as a markup language.
|
|
22
|
+
ADOC
|
|
23
|
+
|
|
24
|
+
DEFAULT_LAYOUT = <<~ERB
|
|
25
|
+
<!DOCTYPE html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8">
|
|
29
|
+
<title><%= page.respond_to?(:title) ? page.title : site.title %></title>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<%= content %>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
ERB
|
|
36
|
+
|
|
37
|
+
DIRS = %w[assets pages _layouts _includes _data _posts _drafts].freeze
|
|
38
|
+
FILES = {
|
|
39
|
+
'config.yml' => CONFIG,
|
|
40
|
+
'pages/index.adoc' => WELCOME,
|
|
41
|
+
'_layouts/default.html.erb' => DEFAULT_LAYOUT
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def self.write(root)
|
|
45
|
+
FileUtils.touch(File.join(root, '.otto'))
|
|
46
|
+
DIRS.each { |dir| FileUtils.mkdir_p(File.join(root, dir)) }
|
|
47
|
+
FILES.each { |path, content| File.write(File.join(root, path), content) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ottogen::Collection do
|
|
4
|
+
describe '#items' do
|
|
5
|
+
it 'reads items from _<name>/' do
|
|
6
|
+
in_tmp_dir do
|
|
7
|
+
FileUtils.mkdir_p('_recipes')
|
|
8
|
+
File.write('_recipes/pizza.adoc', "= Pizza\n")
|
|
9
|
+
File.write('_recipes/bread.adoc', "= Bread\n")
|
|
10
|
+
|
|
11
|
+
collection = described_class.from_config('recipes', 'output' => true)
|
|
12
|
+
|
|
13
|
+
expect(collection.items.map(&:slug)).to contain_exactly('pizza', 'bread')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'returns an empty items list when the directory is missing' do
|
|
18
|
+
in_tmp_dir do
|
|
19
|
+
collection = described_class.from_config('recipes', 'output' => true)
|
|
20
|
+
|
|
21
|
+
expect(collection.items).to eq([])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#output?' do
|
|
27
|
+
it 'reflects the output: setting' do
|
|
28
|
+
expect(described_class.from_config('a', 'output' => true).output?).to be true
|
|
29
|
+
expect(described_class.from_config('b', 'output' => false).output?).to be false
|
|
30
|
+
expect(described_class.from_config('c', {}).output?).to be false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
RSpec.describe Ottogen::CollectionItem do
|
|
36
|
+
describe '#url' do
|
|
37
|
+
it 'is /<collection>/<slug>.html' do
|
|
38
|
+
in_tmp_dir do
|
|
39
|
+
FileUtils.mkdir_p('_recipes')
|
|
40
|
+
File.write('_recipes/pizza.adoc', "= Pizza\n")
|
|
41
|
+
|
|
42
|
+
item = described_class.read('_recipes/pizza.adoc', 'recipes')
|
|
43
|
+
|
|
44
|
+
expect(item.url).to eq('/recipes/pizza.html')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '#output_path' do
|
|
50
|
+
it 'is <build_dir>/<collection>/<slug>.html' do
|
|
51
|
+
in_tmp_dir do
|
|
52
|
+
FileUtils.mkdir_p('_recipes')
|
|
53
|
+
File.write('_recipes/pizza.adoc', "= Pizza\n")
|
|
54
|
+
|
|
55
|
+
item = described_class.read('_recipes/pizza.adoc', 'recipes')
|
|
56
|
+
|
|
57
|
+
expect(item.output_path('_build')).to eq('_build/recipes/pizza.html')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|