1
2
3
4
5
6
7 package com.varmateo.yawg.commonmark;
8
9 import java.nio.file.Path;
10 import java.util.Optional;
11 import java.util.function.Function;
12 import java.util.regex.Pattern;
13
14 import io.vavr.Lazy;
15 import io.vavr.collection.List;
16 import io.vavr.control.Try;
17 import org.commonmark.Extension;
18 import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
19 import org.commonmark.node.Node;
20 import org.commonmark.parser.Parser;
21 import org.commonmark.renderer.html.HtmlRenderer;
22
23 import com.varmateo.yawg.api.Result;
24 import com.varmateo.yawg.spi.PageBakeResult;
25 import com.varmateo.yawg.spi.PageBaker;
26 import com.varmateo.yawg.spi.PageContext;
27 import com.varmateo.yawg.spi.Template;
28 import com.varmateo.yawg.spi.TemplateContext;
29 import com.varmateo.yawg.util.FileUtils;
30 import com.varmateo.yawg.util.PageBakeResults;
31 import com.varmateo.yawg.util.Results;
32
33
34
35
36
37
38 public final class CommonMarkPageBaker
39 implements PageBaker {
40
41
42 private static final String NAME = "markdown";
43
44 private static final Pattern RE_ADOC = Pattern.compile(".*\\.md$");
45
46 private static final String TARGET_EXTENSION = ".html";
47
48 private final Lazy<Parser> _markdownParser =
49 Lazy.of(this::createMarkdownParser);
50
51 private final Lazy<HtmlRenderer> _htmlRenderer =
52 Lazy.of(this::createHtmlRenderer);
53
54 private final Lazy<CommonMarkTemplateContextFactory> _templateContextFactory =
55 Lazy.of(this::createTemplateContextFactory);
56
57
58 private CommonMarkPageBaker() {
59
60 }
61
62
63
64
65
66 public static PageBaker create() {
67
68 return new CommonMarkPageBaker();
69 }
70
71
72
73
74
75 @Override
76 public String shortName() {
77
78 return NAME;
79 }
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 @Override
95 public boolean isBakeable(final Path path) {
96
97 return FileUtils.isNameMatch(path, RE_ADOC);
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 @Override
120 public PageBakeResult bake(
121 final Path sourcePath,
122 final PageContext context,
123 final Path targetDir) {
124
125 final Try<Void> result = doBake(sourcePath, context, targetDir);
126
127 return PageBakeResults.fromTry(result);
128 }
129
130
131
132
133
134 private Try<Void> doBake(
135 final Path sourcePath,
136 final PageContext context,
137 final Path targetDir) {
138
139 final Path targetPath = getTargetPath(sourcePath, targetDir);
140 final Optional<Template> template = context.templateFor(sourcePath);
141 final Try<Void> result;
142
143 if ( template.isPresent() ) {
144 result = doBakeWithTemplate(
145 sourcePath,
146 context,
147 targetDir,
148 targetPath,
149 template.get());
150 } else {
151 result = doBakeWithoutTemplate(
152 sourcePath,
153 targetPath);
154 }
155
156 return result;
157 }
158
159
160
161
162
163 private Path getTargetPath(
164 final Path sourcePath,
165 final Path targetDir) {
166
167 final String sourceBasename = FileUtils.basename(sourcePath);
168 final String targetName = sourceBasename + TARGET_EXTENSION;
169
170 return targetDir.resolve(targetName);
171 }
172
173
174
175
176
177 private Try<Void> doBakeWithoutTemplate(
178 final Path sourcePath,
179 final Path targetPath) {
180
181 return renderBody(sourcePath)
182 .flatMap(body -> renderContentAndSave(sourcePath, targetPath, body));
183 }
184
185
186 private Try<String> renderBody(final Path sourcePath) {
187
188 final Try<Node> document = FileUtils.safeReadFrom(
189 sourcePath,
190 reader -> _markdownParser.get().parseReader(reader));
191 final Try<String> body = document
192 .map(doc -> _htmlRenderer.get().render(doc));
193
194 return body
195 .recoverWith(CommonMarkPageBakerException.commonMarkFailureTry(sourcePath));
196 }
197
198
199 private Try<Void> renderContentAndSave(
200 final Path sourcePath,
201 final Path targetPath,
202 final String body) {
203
204 final String contentTemplate = ""
205 + "<!DOCTYPE html>%n"
206 + "<html><body>%s</body></html>";
207 final String content = String.format(contentTemplate, body);
208 final Try<Void> result = FileUtils.safeWriteTo(
209 targetPath,
210 writer -> writer.write(content));
211
212 return result
213 .recoverWith(CommonMarkPageBakerException.commonMarkFailureTry(sourcePath));
214 }
215
216
217
218
219
220 private Try<Void> doBakeWithTemplate(
221 final Path sourcePath,
222 final PageContext context,
223 final Path targetDir,
224 final Path targetPath,
225 final Template template) {
226
227 final Try<TemplateContext> templateContext = _templateContextFactory.get().build(
228 sourcePath, targetDir, targetPath, context);
229
230 return templateContext.flatMap(processTemplate(sourcePath, targetPath, template));
231 }
232
233
234 private Function<TemplateContext, Try<Void>> processTemplate(
235 final Path sourcePath,
236 final Path targetPath,
237 final Template template) {
238
239 return (TemplateContext templateContext) -> {
240 final Try<Result<Void>> result = FileUtils.safeWriteWith(
241 targetPath,
242 writer -> template.process(templateContext, writer));
243
244 return result
245 .recoverWith(CommonMarkPageBakerException.templateFailureTry(sourcePath))
246 .flatMap(Results::toTry);
247 };
248 }
249
250
251 private Parser createMarkdownParser() {
252
253 final Extension frontMatterExtension = YamlFrontMatterExtension.create();
254 final List<Extension> extensions = List.of(frontMatterExtension);
255
256 return Parser.builder()
257 .extensions(extensions)
258 .build();
259 }
260
261
262 private HtmlRenderer createHtmlRenderer() {
263
264 return HtmlRenderer.builder().build();
265 }
266
267
268 private CommonMarkTemplateContextFactory createTemplateContextFactory() {
269
270 return new CommonMarkTemplateContextFactory(_markdownParser.get(), _htmlRenderer.get());
271 }
272
273
274 }