2023-08-11 15:51:20 +01:00
|
|
|
using Markdig.Helpers;
|
2023-08-10 01:49:09 +01:00
|
|
|
using Markdig.Parsers;
|
|
|
|
|
|
|
|
namespace OliverBooth.Markdown.Timestamp;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Represents a Markdown inline parser that matches Discord-style timestamps.
|
|
|
|
/// </summary>
|
|
|
|
public sealed class TimestampInlineParser : InlineParser
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="TimestampInlineParser" /> class.
|
|
|
|
/// </summary>
|
|
|
|
public TimestampInlineParser()
|
|
|
|
{
|
2024-03-02 00:43:56 +00:00
|
|
|
OpeningCharacters = ['<'];
|
2023-08-10 01:49:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
|
|
|
{
|
|
|
|
// Previous char must be a space
|
|
|
|
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReadOnlySpan<char> span = slice.Text.AsSpan(slice.Start, slice.Length);
|
|
|
|
|
|
|
|
if (!TryConsumeTimestamp(span, out ReadOnlySpan<char> 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; // <t:*> or optionally <t:*:*>
|
|
|
|
slice.Start += rawTimestamp.Length + paddingCount;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool TryConsumeTimestamp(ReadOnlySpan<char> source,
|
|
|
|
out ReadOnlySpan<char> timestamp,
|
|
|
|
out char format)
|
|
|
|
{
|
|
|
|
timestamp = default;
|
|
|
|
format = default;
|
|
|
|
|
2024-02-25 14:11:33 +00:00
|
|
|
if (!source.StartsWith("<t:"))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-08-10 01:49:09 +01:00
|
|
|
timestamp = source[3..];
|
|
|
|
|
|
|
|
if (timestamp.IndexOf('>') == -1)
|
|
|
|
{
|
|
|
|
timestamp = default;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int delimiterIndex = timestamp.IndexOf(':');
|
|
|
|
if (delimiterIndex == 0)
|
|
|
|
{
|
|
|
|
// invalid format <t::*>
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|