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

Added support for specifying frontmatter defaults in _config.yml #306

Merged
merged 2 commits into from
Jul 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions src/Pretzel.Logic/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,29 @@ public interface IConfiguration
bool TryGetValue(string key, out object value);

IDictionary<string, object> ToDictionary();

IDefaultsConfiguration Defaults { get; }
}


internal sealed class Configuration : IConfiguration
{
private const string ConfigFileName = "_config.yml";
public const string DefaultPermalink = "date";

private IDictionary<string, object> _config;
private IFileSystem _fileSystem;
private string _configFilePath;
private IDefaultsConfiguration _defaultsConfiguration;
private readonly IFileSystem _fileSystem;
private readonly string _configFilePath;

public object this[string key]
{
get
{
return _config[key];
}
}
public object this[string key] => _config[key];

public IDefaultsConfiguration Defaults => _defaultsConfiguration;

internal Configuration()
{
_config = new Dictionary<string, object>();
CheckDefaultConfig();
EnsureDefaults();
}

internal Configuration(IFileSystem fileSystem, string sitePath)
Expand All @@ -44,16 +45,14 @@ internal Configuration(IFileSystem fileSystem, string sitePath)
_configFilePath = _fileSystem.Path.Combine(sitePath, ConfigFileName);
}

private void CheckDefaultConfig()
private void EnsureDefaults()
{
if (!_config.ContainsKey("permalink"))
{
_config.Add("permalink", "date");
}
if (!_config.ContainsKey("date"))
{
_config.Add("date", "2012-01-01");
_config.Add("permalink", DefaultPermalink);
}

_defaultsConfiguration = new DefaultsConfiguration(_config);
}

internal void ReadFromFile()
Expand All @@ -62,7 +61,7 @@ internal void ReadFromFile()
if (_fileSystem.File.Exists(_configFilePath))
{
_config = _fileSystem.File.ReadAllText(_configFilePath).ParseYaml();
CheckDefaultConfig();
EnsureDefaults();
}
}

Expand Down
61 changes: 61 additions & 0 deletions src/Pretzel.Logic/DefaultsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.IO;
using Pretzel.Logic.Extensions;

namespace Pretzel.Logic
{
public interface IDefaultsConfiguration
{
IDictionary<string, object> ForScope(string path);
}

internal sealed class DefaultsConfiguration : IDefaultsConfiguration
{
private readonly IDictionary<string, IDictionary<string, object>> _scopedValues;

public DefaultsConfiguration(IDictionary<string, object> configuration)
{
_scopedValues = new Dictionary<string, IDictionary<string, object>>();
FillScopedValues(configuration);
}

private void FillScopedValues(IDictionary<string, object> configuration)
{
if (!configuration.ContainsKey("defaults")) return;

var defaults = configuration["defaults"] as List<object>;
if (defaults == null) return;

foreach (var item in defaults.ConvertAll(x => x as IDictionary<string, object>))
{
if (item != null && item.ContainsKey("scope") && item.ContainsKey("values"))
{
var scopeDictionary = item["scope"] as IDictionary<string, object>;
if (scopeDictionary != null && scopeDictionary.ContainsKey("path"))
{
var path = (string)scopeDictionary["path"];
var values = item["values"] as IDictionary<string, object>;
_scopedValues.Add(path, values ?? new Dictionary<string, object>());
}
}
}
}

public IDictionary<string, object> ForScope(string path)
{
IDictionary<string, object> result = new Dictionary<string, object>();

if (path == null) return result;

if (path.Length > 0)
{
result = result.Merge(ForScope(Path.GetDirectoryName(path)));
}
if (_scopedValues.ContainsKey(path))
{
result = result.Merge(_scopedValues[path]);
}
return result;
}
}
}
28 changes: 28 additions & 0 deletions src/Pretzel.Logic/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;

namespace Pretzel.Logic.Extensions
{
/// <summary>
/// Dictionary extension methods.
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Merges two dictionaries on top of each other and returns a new dictionary.
/// Values from the second override the original values when the key is already present.
/// Values from the second will be added when the key is not present in the first.
/// </summary>
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
{
var result = new Dictionary<TKey,TValue>(first);
if (second != null)
{
foreach (var key in second.Keys)
{
result[key] = second[key];
}
}
return result;
}
}
}
2 changes: 2 additions & 0 deletions src/Pretzel.Logic/Pretzel.Logic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<Compile Include="Commands\CommandParameters.cs" />
<Compile Include="Commands\BaseParameters.cs" />
<Compile Include="Configuration.cs" />
<Compile Include="DefaultsConfiguration.cs" />
<Compile Include="Exceptions\PageProcessingException.cs" />
<Compile Include="Extensibility\Extensions\AzureHostSupport.cs" />
<Compile Include="Extensibility\Extensions\CommonMarkEngine.cs" />
Expand All @@ -105,6 +106,7 @@
<Compile Include="Extensibility\IBeforeProcessingTransform.cs" />
<Compile Include="Extensibility\ITag.cs" />
<Compile Include="Extensibility\TagFactoryBase.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="Extensions\IAssembly.cs" />
<Compile Include="Extensions\Logger.cs" />
<Compile Include="Extensions\Sitemap.cs" />
Expand Down
6 changes: 5 additions & 1 deletion src/Pretzel.Logic/Templating/Context/SiteContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,11 @@ private Page CreatePage(SiteContext context, IConfiguration config, string file,
if (pageCache.ContainsKey(file))
return pageCache[file];
var content = SafeReadContents(file);
var header = content.YamlHeader();

var relativePath = MapToOutputPath(context, file);
var scopedDefaults = context.Config.Defaults.ForScope(relativePath);

var header = scopedDefaults.Merge(content.YamlHeader());

if (header.ContainsKey("published") && header["published"].ToString().ToLower() == "false")
{
Expand Down
14 changes: 14 additions & 0 deletions src/Pretzel.Tests/ConfigurationMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,19 @@ public IDictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>(_config);
}

public IDefaultsConfiguration Defaults
{
get { return new DefaultsConfigurationMock(); }
}
}

internal class DefaultsConfigurationMock : IDefaultsConfiguration
{
public IDictionary<string, object> ForScope(string path)
{
return new Dictionary<string, object>();
}
}

}
83 changes: 83 additions & 0 deletions src/Pretzel.Tests/ConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.IO.Abstractions.TestingHelpers;
using Pretzel.Logic;
using Pretzel.Logic.Extensions;
using Xunit;

namespace Pretzel.Tests
{
public class ConfigurationTests
{
private readonly Configuration _sut;

private const string SampleConfig = @"
pretzel:
engine: liquid

title: 'Site Title'

defaults:
-
scope:
path: ''
values:
author: 'default-author'
-
scope:
path: '_posts'
values:
layout: 'post'
author: 'posts-specific-author'
-
scope:
path: '_posts\2016'
values:
layout: 'post-layout-for-2016'
";

public ConfigurationTests()
{
var fileSystem = new MockFileSystem();
fileSystem.AddFile(@"C:\WebSite\_config.yml", new MockFileData(SampleConfig));

_sut = new Configuration(fileSystem, @"C:\WebSite");
_sut.ReadFromFile();
}

[Fact]
public void Indexer_should_correctly_return_the_value()
{
Assert.Equal("Site Title", _sut["title"]);
}

[Fact]
public void Permalinks_should_be_added_with_default_value_if_not_specified_in_file()
{
Assert.Equal(Configuration.DefaultPermalink, _sut["permalink"]);
}

[Fact]
public void DefaultsForScope_should_layer_the_most_specific_scope_on_top()
{
var defaults = _sut.Defaults.ForScope(@"_posts\2016");

Assert.Equal("post-layout-for-2016", defaults["layout"]);
}

[Fact]
public void DefaultsForScope_should_take_value_from_less_specific_when_not_found_in_most_specific()
{
var defaults = _sut.Defaults.ForScope(@"_posts\2016");

Assert.Equal("posts-specific-author", defaults["author"]);
}

[Fact]
public void DefaultsForScope_should_fallback_to_value_from_empty_path_when_given_path_not_found()
{
var defaults = _sut.Defaults.ForScope("_nonexisting");

Assert.Equal("default-author", defaults["author"]);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test which check that values in page/post's front matter have priority against defaults?

}
48 changes: 48 additions & 0 deletions src/Pretzel.Tests/Extensions/DictionaryExtensionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using Pretzel.Logic.Extensions;
using Xunit;

namespace Pretzel.Tests.Extensions
{
public class DictionaryExtensionTests
{
[Fact]
public void Merge_two_dictionaries_returns_new_merged_dictionary()
{
var first = new Dictionary<string, string>
{
{ "A", "a-first" },
{ "B", "b-first" },
};
var second = new Dictionary<string, string>
{
{ "A", "a-second" },
{ "C", "c-second" },
};

var merged = first.Merge(second);

Assert.Equal(3, merged.Count);
Assert.Equal("a-second", merged["A"]);
Assert.Equal("b-first", merged["B"]);
Assert.Equal("c-second", merged["C"]);
}

[Fact]
public void Merge_with_null_returns_copy_of_first()
{
var first = new Dictionary<string, string>
{
{ "A", "a-first" },
{ "B", "b-first" },
};

var merged = first.Merge(null);

Assert.NotSame(merged, first);
Assert.Equal(2, merged.Count);
Assert.Equal("a-first", merged["A"]);
Assert.Equal("b-first", merged["B"]);
}
}
}
2 changes: 2 additions & 0 deletions src/Pretzel.Tests/Pretzel.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@
<Compile Include="Commands\BaseParameterTests.cs" />
<Compile Include="Commands\CommandExtensions.cs" />
<Compile Include="ConfigurationMock.cs" />
<Compile Include="ConfigurationTests.cs" />
<Compile Include="Extensibility\Extensions\AzureHostSupportTest.cs" />
<Compile Include="Extensibility\Extensions\VirtualDirectorySupportTests.cs" />
<Compile Include="Extensibility\Extensions\WebSequenceDiagramsTests.cs" />
<Compile Include="Extensions\DictionaryExtensionTests.cs" />
<Compile Include="Extensions\SitemapTest.cs" />
<Compile Include="Extensions\StringExtensionsTest.cs" />
<Compile Include="Import\BloggerImportTests.cs" />
Expand Down
Loading