Public Access
Add tray rules and identities editor
PR and Push Build/Test / build-and-test (push) Successful in 7m0s
PR and Push Build/Test / build-and-test (push) Successful in 7m0s
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
namespace MeetingAssistant.Workflow;
|
||||
|
||||
internal enum WorkflowRulesEditorMarkdownInlineStyle
|
||||
{
|
||||
Normal,
|
||||
Italic,
|
||||
Bold,
|
||||
Code
|
||||
}
|
||||
|
||||
internal abstract record WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownParagraph(
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownInline> Inlines) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownCodeBlock(string Text) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownTable(
|
||||
IReadOnlyList<string> Header,
|
||||
IReadOnlyList<IReadOnlyList<string>> Rows) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownInline(
|
||||
string Text,
|
||||
WorkflowRulesEditorMarkdownInlineStyle Style);
|
||||
|
||||
internal static class WorkflowRulesEditorMarkdown
|
||||
{
|
||||
public static IReadOnlyList<WorkflowRulesEditorMarkdownBlock> Parse(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var blocks = new List<WorkflowRulesEditorMarkdownBlock>();
|
||||
var paragraph = new List<string>();
|
||||
var code = new List<string>();
|
||||
var inCodeBlock = false;
|
||||
var lines = ReadLines(text).ToArray();
|
||||
|
||||
for (var index = 0; index < lines.Length; index++)
|
||||
{
|
||||
var line = lines[index];
|
||||
if (line.TrimStart().StartsWith("```", StringComparison.Ordinal))
|
||||
{
|
||||
if (inCodeBlock)
|
||||
{
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownCodeBlock(string.Join(Environment.NewLine, code)));
|
||||
code.Clear();
|
||||
inCodeBlock = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
inCodeBlock = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCodeBlock)
|
||||
{
|
||||
code.Add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTableStart(lines, index))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
var header = ParseTableRow(lines[index]);
|
||||
index += 2;
|
||||
var rows = new List<IReadOnlyList<string>>();
|
||||
while (index < lines.Length && IsTableRow(lines[index]))
|
||||
{
|
||||
rows.Add(ParseTableRow(lines[index]));
|
||||
index++;
|
||||
}
|
||||
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownTable(header, rows));
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
}
|
||||
else
|
||||
{
|
||||
paragraph.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (inCodeBlock)
|
||||
{
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownCodeBlock(string.Join(Environment.NewLine, code)));
|
||||
}
|
||||
|
||||
FlushParagraph(blocks, paragraph);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<WorkflowRulesEditorMarkdownInline> ParseInline(string text)
|
||||
{
|
||||
var result = new List<WorkflowRulesEditorMarkdownInline>();
|
||||
var index = 0;
|
||||
while (index < text.Length)
|
||||
{
|
||||
var next = FindNextMarker(text, index);
|
||||
if (next < 0)
|
||||
{
|
||||
AddInline(result, text[index..], WorkflowRulesEditorMarkdownInlineStyle.Normal);
|
||||
break;
|
||||
}
|
||||
|
||||
if (next > index)
|
||||
{
|
||||
AddInline(result, text[index..next], WorkflowRulesEditorMarkdownInlineStyle.Normal);
|
||||
}
|
||||
|
||||
if (text[next] == '`')
|
||||
{
|
||||
var end = text.IndexOf('`', next + 1);
|
||||
if (end > next)
|
||||
{
|
||||
AddInline(result, text[(next + 1)..end], WorkflowRulesEditorMarkdownInlineStyle.Code);
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text.AsSpan(next).StartsWith("**", StringComparison.Ordinal))
|
||||
{
|
||||
var end = text.IndexOf("**", next + 2, StringComparison.Ordinal);
|
||||
if (end > next)
|
||||
{
|
||||
AddInline(result, text[(next + 2)..end], WorkflowRulesEditorMarkdownInlineStyle.Bold);
|
||||
index = end + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text[next] == '*')
|
||||
{
|
||||
var end = text.IndexOf('*', next + 1);
|
||||
if (end > next)
|
||||
{
|
||||
AddInline(result, text[(next + 1)..end], WorkflowRulesEditorMarkdownInlineStyle.Italic);
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
AddInline(result, text[next].ToString(), WorkflowRulesEditorMarkdownInlineStyle.Normal);
|
||||
index = next + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void FlushParagraph(
|
||||
List<WorkflowRulesEditorMarkdownBlock> blocks,
|
||||
List<string> paragraph)
|
||||
{
|
||||
if (paragraph.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownParagraph(ParseInline(string.Join('\n', paragraph))));
|
||||
paragraph.Clear();
|
||||
}
|
||||
|
||||
private static bool IsTableStart(IReadOnlyList<string> lines, int index)
|
||||
{
|
||||
return index + 1 < lines.Count &&
|
||||
IsTableRow(lines[index]) &&
|
||||
IsTableSeparator(lines[index + 1]);
|
||||
}
|
||||
|
||||
private static bool IsTableRow(string line)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(line) && line.Contains('|', StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool IsTableSeparator(string line)
|
||||
{
|
||||
var cells = ParseTableRow(line);
|
||||
return cells.Count > 0 &&
|
||||
cells.All(cell =>
|
||||
{
|
||||
var trimmed = cell.Trim();
|
||||
return trimmed.Contains('-', StringComparison.Ordinal) &&
|
||||
trimmed.All(character => character is '-' or ':' or ' ');
|
||||
});
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ParseTableRow(string line)
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.StartsWith('|'))
|
||||
{
|
||||
trimmed = trimmed[1..];
|
||||
}
|
||||
|
||||
if (trimmed.EndsWith('|'))
|
||||
{
|
||||
trimmed = trimmed[..^1];
|
||||
}
|
||||
|
||||
return trimmed
|
||||
.Split('|')
|
||||
.Select(cell => cell.Trim())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static void AddInline(
|
||||
List<WorkflowRulesEditorMarkdownInline> result,
|
||||
string text,
|
||||
WorkflowRulesEditorMarkdownInlineStyle style)
|
||||
{
|
||||
if (text.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Count > 0 && result[^1].Style == style)
|
||||
{
|
||||
result[^1] = result[^1] with { Text = result[^1].Text + text };
|
||||
return;
|
||||
}
|
||||
|
||||
result.Add(new WorkflowRulesEditorMarkdownInline(text, style));
|
||||
}
|
||||
|
||||
private static int FindNextMarker(string text, int start)
|
||||
{
|
||||
var code = text.IndexOf('`', start);
|
||||
var bold = text.IndexOf("**", start, StringComparison.Ordinal);
|
||||
var italic = text.IndexOf('*', start);
|
||||
if (italic >= 0 && italic + 1 < text.Length && text[italic + 1] == '*')
|
||||
{
|
||||
italic = text.IndexOf('*', italic + 2);
|
||||
}
|
||||
|
||||
return new[] { code, bold, italic }
|
||||
.Where(index => index >= 0)
|
||||
.DefaultIfEmpty(-1)
|
||||
.Min();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ReadLines(string text)
|
||||
{
|
||||
using var reader = new StringReader(text);
|
||||
while (reader.ReadLine() is { } line)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user