This repository has been archived by the owner on Jun 25, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 163
/
JekyllEngineBase.cs
328 lines (272 loc) · 12.2 KB
/
JekyllEngineBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
using Pretzel.Logic.Exceptions;
using Pretzel.Logic.Extensibility;
using Pretzel.Logic.Extensibility.Extensions;
using Pretzel.Logic.Extensions;
using Pretzel.Logic.Templating.Context;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text.RegularExpressions;
namespace Pretzel.Logic.Templating
{
public abstract class JekyllEngineBase : ISiteEngine
{
private static readonly Regex paragraphRegex = new Regex(@"(<(?:p|h\d{1})>.*?</(?:p|h\d{1})>)", RegexOptions.Compiled | RegexOptions.Singleline);
protected SiteContext Context;
#pragma warning disable 0649
[Import]
public IFileSystem FileSystem { get; set; }
#pragma warning restore 0649
[ImportMany]
public IEnumerable<IFilter> Filters { get; set; }
[ImportMany]
public IEnumerable<ITag> Tags { get; set; }
[ImportMany]
public IEnumerable<TagFactoryBase> TagFactories { get; set; }
[ImportMany]
public IEnumerable<IContentTransform> ContentTransformers;
[Import(AllowDefault = true)]
private ILightweightMarkupEngine _lightweightMarkupEngine;
public abstract void Initialize();
protected abstract void PreProcess();
protected abstract string RenderTemplate(string content, PageContext pageData);
public void Process(SiteContext siteContext, bool skipFileOnError = false)
{
// Default rendering engine
if (_lightweightMarkupEngine == null)
{
_lightweightMarkupEngine = new CommonMarkEngine();
}
Tracing.Debug("LightweightMarkupEngine: {0}", _lightweightMarkupEngine.GetType().Name);
Context = siteContext;
PreProcess();
for (int index = 0; index < siteContext.Posts.Count; index++)
{
var p = siteContext.Posts[index];
var previous = GetPrevious(siteContext.Posts, index);
var next = GetNext(siteContext.Posts, index);
ProcessFile(siteContext.OutputFolder, p, previous, next, skipFileOnError, p.Filepath);
}
for (int index = 0; index < siteContext.Pages.Count; index++)
{
var p = siteContext.Pages[index];
var previous = GetPrevious(siteContext.Pages, index);
var next = GetNext(siteContext.Pages, index);
ProcessFile(siteContext.OutputFolder, p, previous, next, skipFileOnError);
}
}
private static Page GetPrevious(IList<Page> pages, int index)
{
return index < pages.Count - 1 ? pages[index + 1] : null;
}
private static Page GetNext(IList<Page> pages, int index)
{
return index >= 1 ? pages[index - 1] : null;
}
private void ProcessFile(string outputDirectory, Page page, Page previous, Page next, bool skipFileOnError, string relativePath = "")
{
if (string.IsNullOrWhiteSpace(relativePath))
relativePath = MapToOutputPath(page.File);
page.OutputFile = Path.Combine(outputDirectory, relativePath);
var extension = Path.GetExtension(page.File);
if (extension.IsImageFormat())
{
CreateOutputDirectory(page.OutputFile);
CopyFileIfSourceNewer(page.File, page.OutputFile, true);
return;
}
if (page is NonProcessedPage)
{
CreateOutputDirectory(page.OutputFile);
CopyFileIfSourceNewer(page.File, page.OutputFile, true);
return;
}
if (extension.IsMarkdownFile() || extension.IsRazorFile())
{
page.OutputFile = page.OutputFile.Replace(extension, ".html");
}
var pageContext = PageContext.FromPage(Context, page, outputDirectory, page.OutputFile);
pageContext.Previous = previous;
pageContext.Next = next;
var pageContexts = new List<PageContext> { pageContext };
object paginateObj;
if (page.Bag.TryGetValue("paginate", out paginateObj))
{
var paginate = Convert.ToInt32(paginateObj);
var totalPages = (int)Math.Ceiling(Context.Posts.Count / Convert.ToDouble(paginateObj));
var paginator = new Paginator(Context, totalPages, paginate, 1);
pageContext.Paginator = paginator;
var paginateLink = "/page/:page/index.html";
if (page.Bag.ContainsKey("paginate_link"))
paginateLink = Convert.ToString(page.Bag["paginate_link"]);
var prevLink = page.Url;
for (var i = 2; i <= totalPages; i++)
{
var newPaginator = new Paginator(Context, totalPages, paginate, i) { PreviousPageUrl = prevLink };
var link = paginateLink.Replace(":page", Convert.ToString(i));
paginator.NextPageUrl = link;
paginator = newPaginator;
prevLink = link;
var path = Path.Combine(outputDirectory, link.ToRelativeFile());
if (path.EndsWith(FileSystem.Path.DirectorySeparatorChar.ToString()))
{
path = Path.Combine(path, "index.html");
}
var context = new PageContext(pageContext) { Paginator = newPaginator, OutputPath = path };
context.Bag["url"] = link;
pageContexts.Add(context);
}
}
foreach (var context in pageContexts)
{
var metadata = page.Bag;
var failed = false;
var excerptSeparator = context.Bag.ContainsKey("excerpt_separator")
? context.Bag["excerpt_separator"].ToString()
: Context.ExcerptSeparator;
try
{
context.Content = RenderContent(page.File, RenderTemplate(context.Content, context));
context.FullContent = context.Content;
context.Bag["excerpt"] = GetContentExcerpt(context.Content, excerptSeparator);
}
catch (Exception ex)
{
if (!skipFileOnError)
{
var message = string.Format("Failed to process {0}, see inner exception for more details", context.OutputPath);
throw new PageProcessingException(message, ex);
}
Console.WriteLine(@"Failed to process {0}: {1}", context.OutputPath, ex);
continue;
}
while (metadata.ContainsKey("layout"))
{
var layout = metadata["layout"];
if ((string)layout == "nil" || layout == null)
break;
var path = FindLayoutPath(layout.ToString());
if (path == null)
break;
try
{
metadata = ProcessTemplate(context, path);
}
catch (Exception ex)
{
if (!skipFileOnError)
{
var message = string.Format("Failed to process layout {0} for {1}, see inner exception for more details", layout, context.OutputPath);
throw new PageProcessingException(message, ex);
}
Console.WriteLine(@"Failed to process layout {0} for {1} because '{2}'. Skipping file", layout, context.OutputPath, ex.Message);
failed = true;
break;
}
}
if (failed)
{
continue;
}
CreateOutputDirectory(context.OutputPath);
FileSystem.File.WriteAllText(context.OutputPath, context.FullContent);
}
}
private string RenderContent(string file, string contents)
{
string html;
try
{
var contentsWithoutHeader = contents.ExcludeHeader();
html = Path.GetExtension(file).IsMarkdownFile()
? _lightweightMarkupEngine.Convert(contentsWithoutHeader).Trim()
: contentsWithoutHeader;
if (ContentTransformers != null)
{
html = ContentTransformers.Aggregate(html, (current, contentTransformer) => contentTransformer.Transform(current));
}
}
catch (Exception e)
{
Tracing.Info("Error ({0}) converting {1}", e.Message, file);
Tracing.Debug(e.ToString());
html = String.Format("<p><b>Error converting markdown:</b><br />{0}</p><p>Original content:<br /><pre>{1}</pre></p>", e.Message, contents);
}
return html;
}
private static string GetContentExcerpt(string content, string excerptSeparator)
{
var excerptSeparatorIndex = content.IndexOf(excerptSeparator, StringComparison.InvariantCulture);
string excerpt = null;
if (excerptSeparatorIndex == -1)
{
var match = paragraphRegex.Match(content);
if (match.Success)
{
excerpt = match.Groups[1].Value;
}
}
else
{
excerpt = content.Substring(0, excerptSeparatorIndex);
if (excerpt.StartsWith("<p>") && !excerpt.EndsWith("</p>"))
{
excerpt += "</p>";
}
}
return excerpt;
}
public void CopyFileIfSourceNewer(string sourceFileName, string destFileName, bool overwrite)
{
if (!FileSystem.File.Exists(destFileName) ||
FileSystem.File.GetLastWriteTime(sourceFileName) > FileSystem.File.GetLastWriteTime(destFileName))
{
FileSystem.File.Copy(sourceFileName, destFileName, overwrite);
}
}
private void CreateOutputDirectory(string outputFile)
{
var directory = Path.GetDirectoryName(outputFile);
if (!FileSystem.Directory.Exists(directory))
FileSystem.Directory.CreateDirectory(directory);
}
private static readonly string[] layoutExtensions = { ".html", ".htm" };
protected virtual string[] LayoutExtensions
{
get { return layoutExtensions; }
}
private IDictionary<string, object> ProcessTemplate(PageContext pageContext, string path)
{
var templateFile = FileSystem.File.ReadAllText(path);
var metadata = templateFile.YamlHeader();
var templateContent = templateFile.ExcludeHeader();
pageContext.FullContent = RenderTemplate(templateContent, pageContext);
return metadata;
}
private string MapToOutputPath(string file)
{
var temp = file.Replace(Context.SourceFolder, "")
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return temp;
}
public bool CanProcess(SiteContext context)
{
var engineInfo = GetType().GetCustomAttributes(typeof(SiteEngineInfoAttribute), true).SingleOrDefault() as SiteEngineInfoAttribute;
if (engineInfo == null) return false;
return context.Engine == engineInfo.Engine;
}
private string FindLayoutPath(string layout)
{
foreach (var extension in LayoutExtensions)
{
var path = Path.Combine(Context.SourceFolder, "_layouts", layout + extension);
if (FileSystem.File.Exists(path))
return path;
}
return null;
}
}
}