Skip to content
This repository has been archived by the owner on Jun 25, 2020. It is now read-only.

Commit

Permalink
Fix #225
Browse files Browse the repository at this point in the history
  • Loading branch information
Jérémie Bertrand committed Mar 11, 2015
1 parent 26aa76a commit 01c7dbc
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 159 deletions.
4 changes: 2 additions & 2 deletions src/Pretzel.Logic/Pretzel.Logic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
</Compile>
<Compile Include="SanityCheck.cs" />
<Compile Include="Templating\Context\Category.cs" />
<Compile Include="Templating\Context\LinkHelper.cs" />
<Compile Include="Templating\Context\NonProcessedPage.cs" />
<Compile Include="Templating\Context\Page.cs" />
<Compile Include="Templating\Context\Paginator.cs" />
Expand Down Expand Up @@ -286,5 +287,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>

</Project>
108 changes: 108 additions & 0 deletions src/Pretzel.Logic/Templating/Context/LinkHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Pretzel.Logic.Templating.Context
{
[Export]
public sealed class LinkHelper
{
private static readonly Regex TimestampAndTitleFromPathRegex = new Regex(@"\\(?:(?<timestamp>\d+-\d+-\d+)-)?(?<title>[^\\]*)\.[^\.]+$", RegexOptions.Compiled);
private static readonly Regex CategoryRegex = new Regex(@":category(\d*)", RegexOptions.Compiled);
private static readonly Regex SlashesRegex = new Regex(@"/{1,}", RegexOptions.Compiled);

private static readonly string[] HtmlExtensions = new[] { ".markdown", ".mdown", ".mkdn", ".mkd", ".md", ".textile" };

private static readonly Dictionary<string, string> BuiltInPermalinks = new Dictionary<string, string>
{
{ "date", "/:categories/:year/:month/:day/:title.html" },
{ "pretty", "/:categories/:year/:month/:day/:title/" },
{ "ordinal", "/:categories/:year/:y_day/:title.html" },
{ "none", "/:categories/:title.html" },
};

// http://jekyllrb.com/docs/permalinks/
public string EvaluatePermalink(string permalink, Page page)
{
if (BuiltInPermalinks.ContainsKey(permalink))
{
permalink = BuiltInPermalinks[permalink];
}

permalink = permalink.Replace(":categories", string.Join("/", page.Categories.ToArray()));
permalink = permalink.Replace(":dashcategories", string.Join("-", page.Categories.ToArray()));
permalink = permalink.Replace(":year", page.Date.Year.ToString(CultureInfo.InvariantCulture));
permalink = permalink.Replace(":month", page.Date.ToString("MM"));
permalink = permalink.Replace(":day", page.Date.ToString("dd"));
permalink = permalink.Replace(":title", GetTitle(page.File));
permalink = permalink.Replace(":y_day", page.Date.DayOfYear.ToString("000"));
permalink = permalink.Replace(":short_year", page.Date.ToString("yy"));
permalink = permalink.Replace(":i_month", page.Date.Month.ToString());
permalink = permalink.Replace(":i_day", page.Date.Day.ToString());

if (permalink.Contains(":category"))
{
var matches = CategoryRegex.Matches(permalink);
if (matches != null && matches.Count > 0)
{
foreach (Match match in matches)
{
var replacementValue = string.Empty;
int categoryIndex;
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out categoryIndex) && categoryIndex > 0)
{
replacementValue = page.Categories.Skip(categoryIndex - 1).FirstOrDefault();
}
else if (page.Categories.Any())
{
replacementValue = page.Categories.First();
}
}

permalink = permalink.Replace(match.Value, replacementValue);
}
}
}

permalink = SlashesRegex.Replace(permalink, "/");

return permalink;
}

public string EvaluateLink(SiteContext context, Page page)
{
var directory = Path.GetDirectoryName(page.Filepath);
var relativePath = directory.Replace(context.OutputFolder, string.Empty);
var fileExtension = Path.GetExtension(page.Filepath);

if (HtmlExtensions.Contains(fileExtension, StringComparer.InvariantCultureIgnoreCase))
{
fileExtension = ".html";
}

var link = relativePath.Replace('\\', '/').TrimStart('/') + "/" + GetPageTitle(page.Filepath) + fileExtension;
if (!link.StartsWith("/"))
{
link = "/" + link;
}

return link;
}

public string GetTitle(string file)
{
return TimestampAndTitleFromPathRegex.Match(file).Groups["title"].Value;
}

private string GetPageTitle(string file)
{
return Path.GetFileNameWithoutExtension(file);
}
}
}
93 changes: 15 additions & 78 deletions src/Pretzel.Logic/Templating/Context/SiteContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,26 @@
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Pretzel.Logic.Templating.Context
{
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class SiteContextGenerator
{
private static readonly Regex categoryRegex = new Regex(@":category(\d*)", RegexOptions.Compiled);
private static readonly Regex slashesRegex = new Regex(@"/{1,}", RegexOptions.Compiled);

private readonly Dictionary<string, Page> pageCache = new Dictionary<string, Page>();
private readonly IFileSystem fileSystem;
private readonly IEnumerable<IContentTransform> contentTransformers;
private readonly List<string> includes = new List<string>();
private readonly List<string> excludes = new List<string>();
private readonly LinkHelper linkHelper;

[ImportingConstructor]
public SiteContextGenerator(IFileSystem fileSystem, [ImportMany]IEnumerable<IContentTransform> contentTransformers)
public SiteContextGenerator(IFileSystem fileSystem, [ImportMany]IEnumerable<IContentTransform> contentTransformers, LinkHelper linkHelper)
{
this.fileSystem = fileSystem;
this.contentTransformers = contentTransformers;
this.linkHelper = linkHelper;
}

public SiteContext BuildContext(string path, string destinationPath, bool includeDrafts)
Expand All @@ -42,7 +40,9 @@ public SiteContext BuildContext(string path, string destinationPath, bool includ
config = (Dictionary<string, object>)fileSystem.File.ReadAllText(configPath).YamlHeader(true);

if (!config.ContainsKey("permalink"))
config.Add("permalink", "/:year/:month/:day/:title.html");
{
config.Add("permalink", "date");
}

if (config.ContainsKey("pretzel"))
{
Expand Down Expand Up @@ -252,11 +252,17 @@ private Page CreatePage(SiteContext context, IDictionary<string, object> config,

// resolve permalink
if (header.ContainsKey("permalink"))
page.Url = EvaluatePermalink(header["permalink"].ToString(), page);
{
page.Url = linkHelper.EvaluatePermalink(header["permalink"].ToString(), page);
}
else if (isPost && config.ContainsKey("permalink"))
page.Url = EvaluatePermalink(config["permalink"].ToString(), page);
{
page.Url = linkHelper.EvaluatePermalink(config["permalink"].ToString(), page);
}
else
page.Url = EvaluateLink(context, page);
{
page.Url = linkHelper.EvaluateLink(context, page);
}

// resolve id
page.Id = page.Url.Replace(".html", string.Empty).Replace("index", string.Empty);
Expand Down Expand Up @@ -308,23 +314,6 @@ private string GetFilePathForPage(SiteContext context, string file)
return Path.Combine(context.OutputFolder, MapToOutputPath(context, file));
}

private string EvaluateLink(SiteContext context, Page page)
{
var directory = Path.GetDirectoryName(page.Filepath);
var relativePath = directory.Replace(context.OutputFolder, string.Empty);
var fileExtension = Path.GetExtension(page.Filepath);

var htmlExtensions = new[] { ".markdown", ".mdown", ".mkdn", ".mkd", ".md", ".textile" };

if (htmlExtensions.Contains(fileExtension, StringComparer.InvariantCultureIgnoreCase))
fileExtension = ".html";

var link = relativePath.Replace('\\', '/').TrimStart('/') + "/" + GetPageTitle(page.Filepath) + fileExtension;
if (!link.StartsWith("/"))
link = "/" + link;
return link;
}

private IEnumerable<Page> GetDirectoryPages(SiteContext context, IDictionary<string, object> config, string forDirectory, bool isPost)
{
return fileSystem
Expand Down Expand Up @@ -416,46 +405,6 @@ private string SafeReadContents(string file)
}
}

// https://github.com/mojombo/jekyll/wiki/permalinks
private string EvaluatePermalink(string permalink, Page page)
{
permalink = permalink.Replace(":categories", string.Join("-", page.Categories.ToArray()));
permalink = permalink.Replace(":year", page.Date.Year.ToString(CultureInfo.InvariantCulture));
permalink = permalink.Replace(":month", page.Date.ToString("MM"));
permalink = permalink.Replace(":day", page.Date.ToString("dd"));
permalink = permalink.Replace(":title", GetTitle(page.File));

if (permalink.Contains(":category"))
{
var matches = categoryRegex.Matches(permalink);
if (matches != null && matches.Count > 0)
{
foreach (Match match in matches)
{
var replacementValue = string.Empty;
int categoryIndex;
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out categoryIndex) && categoryIndex > 0)
{
replacementValue = page.Categories.Skip(categoryIndex - 1).FirstOrDefault();
}
else if (page.Categories.Any())
{
replacementValue = page.Categories.First();
}
}

permalink = permalink.Replace(match.Value, replacementValue);
}
}
}

permalink = slashesRegex.Replace(permalink, "/");

return permalink;
}

// http://stackoverflow.com/questions/6716832/sanitizing-string-to-url-safe-format
public static string RemoveDiacritics(string strThis)
{
Expand Down Expand Up @@ -489,17 +438,5 @@ private string GetPathWithTimestamp(string outputDirectory, string file)
var title = string.Join("-", tokens.Skip(3));
return Path.Combine(outputDirectory, timestamp, title);
}

private static readonly Regex TimestampAndTitleFromPathRegex = new Regex(@"\\(?:(?<timestamp>\d+-\d+-\d+)-)?(?<title>[^\\]*)\.[^\.]+$");

public static string GetTitle(string file)
{
return TimestampAndTitleFromPathRegex.Match(file).Groups["title"].Value;
}

private string GetPageTitle(string file)
{
return Path.GetFileNameWithoutExtension(file);
}
}
}
1 change: 1 addition & 0 deletions src/Pretzel.Tests/Pretzel.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<Compile Include="PretzelContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Recipe\RecipeTests.cs" />
<Compile Include="Templating\Context\LinkHelperTest.cs" />
<Compile Include="Templating\Context\SiteContextGeneratorTests.cs" />
<Compile Include="Templating\JekyllEngineBaseTest.cs" />
<Compile Include="Templating\Jekyll\BakingEnvironment.cs" />
Expand Down
82 changes: 82 additions & 0 deletions src/Pretzel.Tests/Templating/Context/LinkHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using Pretzel.Logic.Templating.Context;
using System;
using System.Linq;
using Xunit;
using Xunit.Extensions;

namespace Pretzel.Tests.Templating.Context
{
public class LinkHelperTest
{
public LinkHelper LinkHelper { get; private set; }

public LinkHelperTest()
{
LinkHelper = new LinkHelper();
}

[Fact]
public void GetTitle_returns_original_value_when_no_timestamp()
{
var result = LinkHelper.GetTitle(@"C:\temp\foobar_baz.md");
Assert.Equal("foobar_baz", result);
}

[Fact]
public void GetTitle_returns_strips_timestamp()
{
var result = LinkHelper.GetTitle(@"C:\temp\2012-01-03-foobar_baz.md");
Assert.Equal("foobar_baz", result);
}

[Fact]
public void GetTitle_preserves_dash_separated_values_that_arent_timestamps()
{
var result = LinkHelper.GetTitle(@"C:\temp\foo-bar-baz-qak-foobar_baz.md");
Assert.Equal("foo-bar-baz-qak-foobar_baz", result);
}

[InlineData(@"C:\TestSite\_site\about.md", "/about.html")]
[InlineData(@"C:\TestSite\_site\about.mkd", "/about.html")]
[InlineData(@"C:\TestSite\_site\about.mkdn", "/about.html")]
[InlineData(@"C:\TestSite\_site\about.mdown", "/about.html")]
[InlineData(@"C:\TestSite\_site\about.markdown", "/about.html")]
[InlineData(@"C:\TestSite\_site\about.textile", "/about.html")]
[InlineData(@"C:\TestSite\_site\rss.xml", "/rss.xml")]
[InlineData(@"C:\TestSite\_site\relativepath\about.md", "/relativepath/about.html")]
[Theory]
public void EvaluateLink_url_is_well_formatted(string filePath, string expectedUrl)
{
var siteContext = new SiteContext { OutputFolder = @"C:\TestSite\_site" };
var page = new Page { Filepath = filePath };

Assert.Equal(expectedUrl, LinkHelper.EvaluateLink(siteContext, page));
}

[InlineData("date", "/cat1/cat2/2015/03/09/foobar-baz.html", "cat1,cat2")]
[InlineData("date", "/2015/03/09/foobar-baz.html", "")]
[InlineData("/:dashcategories/:year/:month/:day/:title.html", "/cat1-cat2/2015/03/09/foobar-baz.html", "cat1,cat2")]
[InlineData("/:dashcategories/:year/:month/:day/:title.html", "/2015/03/09/foobar-baz.html", "")]
[InlineData("pretty", "/cat1/cat2/2015/03/09/foobar-baz/", "cat1,cat2")]
[InlineData("ordinal", "/cat1/cat2/2015/068/foobar-baz.html", "cat1,cat2")]
[InlineData("none", "/cat1/cat2/foobar-baz.html", "cat1,cat2")]
[InlineData("/:categories/:short_year/:i_month/:i_day/:title.html", "/cat1/cat2/15/3/9/foobar-baz.html", "cat1,cat2")]
[InlineData("/:category/:title.html", "/cat1/foobar-baz.html", "cat1,cat2")]
[InlineData("/:category/:title.html", "/foobar-baz.html", "")]
[InlineData("/:category1/:title.html", "/cat1/foobar-baz.html", "cat1,cat2")]
[InlineData("/:category2/:title.html", "/cat2/foobar-baz.html", "cat1,cat2")]
[InlineData("/:category3/:title.html", "/foobar-baz.html", "cat1,cat2")]
[Theory]
public void EvaluatePermalink_url_is_well_formatted(string permalink, string expectedUrl, string categories)
{
var page = new Page
{
Categories = categories == null ? Enumerable.Empty<string>() : categories.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
Date = new DateTime(2015, 03, 09),
File = @"C:\temp\2015-03-09-foobar-baz.md"
};

Assert.Equal(expectedUrl, LinkHelper.EvaluatePermalink(permalink, page));
}
}
}
Loading

0 comments on commit 01c7dbc

Please sign in to comment.