164 lines
5.3 KiB
C#
164 lines
5.3 KiB
C#
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
using Gallium;
|
||
|
using Nitride.Contents;
|
||
|
using YamlDotNet.Serialization;
|
||
|
using YamlDotNet.Serialization.NamingConventions;
|
||
|
|
||
|
namespace Nitride.Yaml
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// An operation that parses the header of a text file and pulls out the
|
||
|
/// YAML header with a specific type.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="TModel">The model that represents the header.</typeparam>
|
||
|
public class ParseYamlHeader<TModel> : NitrideOperationBase
|
||
|
where TModel : class, new()
|
||
|
{
|
||
|
private bool ignoreUnmatchedProperties;
|
||
|
|
||
|
private INamingConvention namingConvention;
|
||
|
|
||
|
public ParseYamlHeader()
|
||
|
{
|
||
|
this.namingConvention = CamelCaseNamingConvention.Instance;
|
||
|
this.ignoreUnmatchedProperties = true;
|
||
|
this.RemoveHeader = true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether the header should be removed
|
||
|
/// and the text content be removed.
|
||
|
/// </summary>
|
||
|
private bool RemoveHeader { get; set; }
|
||
|
|
||
|
/// <inheritdoc />
|
||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||
|
{
|
||
|
// Set up the YAML parsing.
|
||
|
DeserializerBuilder builder = new DeserializerBuilder()
|
||
|
.WithNamingConvention(this.namingConvention);
|
||
|
|
||
|
if (this.ignoreUnmatchedProperties)
|
||
|
{
|
||
|
builder = builder.IgnoreUnmatchedProperties();
|
||
|
}
|
||
|
|
||
|
IDeserializer deserializer = builder.Build();
|
||
|
|
||
|
// Process through the files. We only care about the text ones
|
||
|
// and we'll put a default TModel in for those that don't have a
|
||
|
// header.
|
||
|
return input
|
||
|
.ForEachEntity<ITextContent>(
|
||
|
(entity, content) => this.Parse(
|
||
|
entity,
|
||
|
content,
|
||
|
deserializer));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the flag if the module should ignore unmatched properties
|
||
|
/// which defaults to true (ignore).
|
||
|
/// </summary>
|
||
|
/// <param name="value">The new value.</param>
|
||
|
/// <returns>The module for chaining.</returns>
|
||
|
public ParseYamlHeader<TModel> WithIgnoreUnmatchedProperties(bool value)
|
||
|
{
|
||
|
this.ignoreUnmatchedProperties = value;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the naming convention to something other than the default
|
||
|
/// camel case.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The new naming convention.</param>
|
||
|
/// <returns>The module for chaining.</returns>
|
||
|
public ParseYamlHeader<TModel> WithNamingConvention(
|
||
|
INamingConvention value)
|
||
|
{
|
||
|
this.namingConvention = value;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public ParseYamlHeader<TModel> WithRemoveHeader(bool value)
|
||
|
{
|
||
|
this.RemoveHeader = value;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
private Entity Parse(
|
||
|
Entity entity,
|
||
|
ITextContent content,
|
||
|
IDeserializer deserializer)
|
||
|
{
|
||
|
// Get the textual input from the stream.
|
||
|
using TextReader reader = content.GetReader();
|
||
|
|
||
|
// See if the first line is one that indicates a YAML header.
|
||
|
string? line = reader.ReadLine();
|
||
|
|
||
|
if (line != "---")
|
||
|
{
|
||
|
// This file doesn't have a YAML header, so add the default
|
||
|
// version of our model and move on.
|
||
|
return entity.Set(new TModel());
|
||
|
}
|
||
|
|
||
|
// Read the rest of the header until we get to the end of the
|
||
|
// header. If we get to the end of the file first, then we don't
|
||
|
// have a valid file and just return the default.
|
||
|
StringBuilder buffer = new();
|
||
|
|
||
|
buffer.AppendLine(line);
|
||
|
|
||
|
while ((line = reader.ReadLine()) != null)
|
||
|
{
|
||
|
// If have a separator, then we're done processing.
|
||
|
if (line == "---")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Read the next line into memory.
|
||
|
buffer.AppendLine(line);
|
||
|
}
|
||
|
|
||
|
// If the line is null, then we got to the end of the file without
|
||
|
// finding a proper header, so use a default and have no other
|
||
|
// changes.
|
||
|
if (line == null)
|
||
|
{
|
||
|
return entity.Set(new TModel());
|
||
|
}
|
||
|
|
||
|
// Pull out the model so we can append it later.
|
||
|
string yaml = buffer.ToString();
|
||
|
|
||
|
var model = deserializer.Deserialize<TModel>(yaml);
|
||
|
|
||
|
// If we are not removing the header, then we're done.
|
||
|
if (!this.RemoveHeader)
|
||
|
{
|
||
|
return entity.Set(model);
|
||
|
}
|
||
|
|
||
|
// Read the rest of the reader into a new (reused) buffer so we can
|
||
|
// set the new text content to the text without the header.
|
||
|
buffer.Length = 0;
|
||
|
|
||
|
while ((line = reader.ReadLine()) != null)
|
||
|
{
|
||
|
buffer.AppendLine(line);
|
||
|
}
|
||
|
|
||
|
// Set the model and return it.
|
||
|
return entity
|
||
|
.Set(model)
|
||
|
.SetTextContent(new StringTextContent(buffer.ToString()));
|
||
|
}
|
||
|
}
|
||
|
}
|