using Markdig.Helpers;
using Markdig.Parsers;
namespace OliverBooth.Markdown.Timestamp;
///
/// Represents a Markdown inline parser that matches Discord-style timestamps.
///
public sealed class TimestampInlineParser : InlineParser
{
///
/// Initializes a new instance of the class.
///
public TimestampInlineParser()
{
OpeningCharacters = ['<'];
}
///
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Previous char must be a space
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
{
return false;
}
ReadOnlySpan span = slice.Text.AsSpan(slice.Start, slice.Length);
if (!TryConsumeTimestamp(span, out ReadOnlySpan rawTimestamp, out char format))
{
return false;
}
if (!long.TryParse(rawTimestamp, out long timestamp))
{
return false;
}
bool hasFormat = format != '\0';
processor.Inline = new TimestampInline
{
Format = (TimestampFormat)format,
Timestamp = DateTimeOffset.FromUnixTimeSeconds(timestamp)
};
int paddingCount = hasFormat ? 6 : 4; // or optionally
slice.Start += rawTimestamp.Length + paddingCount;
return true;
}
private bool TryConsumeTimestamp(ReadOnlySpan source,
out ReadOnlySpan timestamp,
out char format)
{
timestamp = default;
format = default;
if (!source.StartsWith("') == -1)
{
timestamp = default;
return false;
}
int delimiterIndex = timestamp.IndexOf(':');
if (delimiterIndex == 0)
{
// invalid format
timestamp = default;
return false;
}
if (delimiterIndex == -1)
{
// no format, default to relative
format = 'R';
timestamp = timestamp[..^1]; // trim >
}
else
{
// use specified format
format = timestamp[^2];
timestamp = timestamp[..^3];
}
return true;
}
}