Skip to content

Commit 3255915

Browse files
committed
Beginnings of a unified output presenter
It can render text as markdown with: * standard markdown markup * no markup at all * color instead of markup
1 parent e9902c2 commit 3255915

File tree

7 files changed

+344
-35
lines changed

7 files changed

+344
-35
lines changed

tools/apput/src/Markdown/MarkdownDocument.cs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,99 +29,94 @@ public void AddNewLine ()
2929
elements.Add (CreateNewLine ());
3030
}
3131

32-
public string Render (bool renderPlainText)
32+
public MarkdownPresenter Render (bool toConsole, bool useColor, bool renderPlainText)
3333
{
34+
var presenter = new MarkdownPresenter (toConsole, useColor, renderPlainText);
3435
if (IsEmpty) {
35-
return String.Empty;
36+
return presenter;
3637
}
3738

38-
var sb = new StringBuilder ();
3939
foreach (MarkdownElement element in elements) {
40-
RenderSafe (sb, element, renderPlainText);
40+
RenderSafe (presenter, element, renderPlainText);
4141
}
4242

43-
return sb.ToString ();
43+
return presenter;
4444
}
4545

46-
void RenderSafe (StringBuilder sb, MarkdownElement element, bool plain)
46+
void RenderSafe (MarkdownPresenter presenter, MarkdownElement element, bool plain)
4747
{
4848
try {
49-
Render (sb, element, plain);
49+
Render (presenter, element, plain);
5050
} catch (Exception ex) {
5151
Log.Warning ($"Failed to render element {element}.", ex);
5252
}
5353
}
5454

55-
void RenderChildren (StringBuilder sb, MarkdownContainerElement element, bool plain)
55+
void RenderChildren (MarkdownPresenter presenter, MarkdownContainerElement element, bool plain)
5656
{
5757
if (element.Children == null || element.Children.Count == 0) {
5858
return;
5959
}
6060

6161
foreach (MarkdownElement child in element.Children) {
62-
RenderSafe (sb, child, plain);
62+
RenderSafe (presenter, child, plain);
6363
}
6464
}
6565

66-
void Render (StringBuilder sb, MarkdownElement element, bool plain)
66+
void Render (MarkdownPresenter presenter, MarkdownElement element, bool plain)
6767
{
6868
if (element is MarkdownTextSpan textSpan) {
69-
Render (sb, textSpan, plain);
69+
Render (presenter, textSpan, plain);
7070
} else if (element is MarkdownHeading section) {
71-
Render (sb, section, plain);
71+
Render (presenter, section, plain);
7272
} else if (element is MarkdownParagraph para) {
73-
Render (sb, para, plain);
73+
Render (presenter, para, plain);
7474
} else {
7575
throw new InvalidOperationException ($"Internal error: Markdown element {element.GetType ()} not supported when rendering.");
7676
}
7777
}
7878

79-
void Render (StringBuilder sb, MarkdownParagraph para, bool plain)
79+
void Render (MarkdownPresenter presenter, MarkdownParagraph para, bool plain)
8080
{
81-
RenderChildren (sb, para, plain);
81+
RenderChildren (presenter, para, plain);
8282
int newLines = para.GetNumberOfNewLinesNeeded ();
8383
if (newLines > 0) {
8484
for (int i = 0; i < newLines; i++) {
85-
AddNewLine (sb);
85+
presenter.AddNewLine ();
8686
}
8787
}
8888
}
8989

90-
void Render (StringBuilder sb, MarkdownHeading section, bool plain)
90+
void Render (MarkdownPresenter presenter, MarkdownHeading section, bool plain)
9191
{
92-
sb.Append ('#', (int)section.Level);
93-
sb.Append (' ');
94-
sb.Append (section.Text);
95-
AddNewLine (sb);
96-
AddNewLine (sb);
92+
presenter.Append ('#', (int)section.Level);
93+
presenter.Append (' ');
94+
presenter.Append (section.Text);
95+
presenter.AddNewLine ();
96+
presenter.AddNewLine ();
9797

98-
RenderChildren (sb, section, plain);
98+
RenderChildren (presenter, section, plain);
9999
}
100100

101-
void Render (StringBuilder sb, MarkdownTextSpan textSpan, bool plain)
101+
void Render (MarkdownPresenter presenter, MarkdownTextSpan textSpan, bool plain)
102102
{
103103
if (textSpan.IsEmpty) {
104104
return;
105105
}
106106

107-
const string Bold = "**";
108-
109107
RenderSpan (textSpan);
110108
foreach (MarkdownTextSpan fragment in textSpan.Fragments) {
111109
RenderSpan (fragment);
112110
}
113111

114112
void RenderSpan (MarkdownTextSpan span)
115113
{
116-
if (!plain && span.Bold) {
117-
sb.Append (Bold);
118-
}
114+
MarkdownTextStyle style = (!plain && span.Bold) switch {
115+
true => MarkdownTextStyle.Bold,
116+
false => MarkdownTextStyle.Plain
117+
};
119118

120-
sb.Append (span.Text);
121-
122-
if (!plain && span.Bold) {
123-
sb.Append (Bold);
124-
}
119+
presenter.Append (span.Text, style);
125120
}
126121
}
127122

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
3+
namespace ApplicationUtility;
4+
5+
partial class MarkdownPresenter
6+
{
7+
abstract class BasePresenter
8+
{
9+
protected const string BoldMarker = "**";
10+
protected const string ItalicMarker = "_";
11+
protected const string MonospaceMarker = "`";
12+
13+
public abstract bool RendersToString { get; }
14+
15+
public abstract void Append (string? text);
16+
public abstract void Append (char ch);
17+
18+
public virtual string AsString ()
19+
{
20+
throw new InvalidOperationException ("Internal error: inner presenter cannot render to string.");
21+
}
22+
23+
public void AddNewLine () => Append (Environment.NewLine);
24+
25+
public void Append (char ch, int count) => Append (new String (ch, count));
26+
27+
public virtual void StartBold (object? state) => Append (BoldMarker);
28+
public virtual void EndBold (object? state) => Append (BoldMarker);
29+
30+
public virtual void StartItalic (object? state) => Append (ItalicMarker);
31+
public virtual void EndItalic (object? state) => Append (ItalicMarker);
32+
33+
public virtual void StartMonospace (object? state) => Append (MonospaceMarker);
34+
public virtual void EndMonospace (object? state) => Append (MonospaceMarker);
35+
36+
public virtual object? SaveState () => null;
37+
public virtual void RestoreState (object? state) {}
38+
}
39+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace ApplicationUtility;
5+
6+
partial class MarkdownPresenter
7+
{
8+
class ConsolePresenter : BasePresenter
9+
{
10+
sealed class ColorState
11+
{
12+
public ConsoleColor Foreground;
13+
public ConsoleColor Background;
14+
public bool BoldEnabled;
15+
public bool ItalicEnabled;
16+
}
17+
18+
const ConsoleColor Bold = ConsoleColor.White;
19+
const ConsoleColor Italic = ConsoleColor.Cyan;
20+
const ConsoleColor BoldItalicFg = ConsoleColor.Gray;
21+
const ConsoleColor BoldItalicBg = ConsoleColor.DarkRed;
22+
23+
readonly bool useColor;
24+
25+
public override bool RendersToString => false;
26+
27+
public ConsolePresenter (bool useColor)
28+
{
29+
this.useColor = useColor;
30+
}
31+
32+
public override void Append (string? text) => Console.Write (text);
33+
public override void Append (char ch) => Console.Write (ch);
34+
35+
public override object? SaveState ()
36+
{
37+
if (!useColor) {
38+
return null;
39+
}
40+
41+
return new ColorState {
42+
Foreground = Console.ForegroundColor,
43+
Background = Console.BackgroundColor,
44+
};
45+
}
46+
47+
public override void RestoreState (object? state)
48+
{
49+
var colors = state as ColorState;
50+
if (colors == null) {
51+
return;
52+
}
53+
54+
Console.ForegroundColor = colors.Foreground;
55+
Console.BackgroundColor = colors.Background;
56+
}
57+
58+
public override void StartBold (object? state)
59+
{
60+
if (!useColor) {
61+
base.StartBold (state);
62+
return;
63+
}
64+
65+
var colors = state as ColorState;
66+
if (colors == null) {
67+
Console.ForegroundColor = Bold;
68+
return;
69+
}
70+
71+
colors.BoldEnabled = true;
72+
if (colors.ItalicEnabled) {
73+
Console.ForegroundColor = BoldItalicFg;
74+
Console.BackgroundColor = BoldItalicBg;
75+
} else {
76+
Console.ForegroundColor = Bold;
77+
}
78+
}
79+
80+
public override void EndBold (object? state)
81+
{
82+
var colors = state as ColorState;
83+
if (colors == null) {
84+
base.EndBold (state);
85+
return;
86+
}
87+
88+
if (colors.ItalicEnabled) {
89+
Console.ForegroundColor = Italic;
90+
Console.BackgroundColor = colors.Background;
91+
} else {
92+
Console.ForegroundColor = colors.Foreground;
93+
}
94+
colors.BoldEnabled = false;
95+
}
96+
97+
public override void StartItalic (object? state)
98+
{
99+
if (!useColor) {
100+
base.StartItalic (state);
101+
return;
102+
}
103+
104+
var colors = state as ColorState;
105+
if (colors == null) {
106+
Console.ForegroundColor = Italic;
107+
return;
108+
}
109+
110+
colors.ItalicEnabled = true;
111+
if (colors.BoldEnabled) {
112+
Console.ForegroundColor = BoldItalicFg;
113+
Console.BackgroundColor = BoldItalicBg;
114+
} else {
115+
Console.ForegroundColor = Italic;
116+
}
117+
}
118+
119+
public override void EndItalic (object? state)
120+
{
121+
var colors = state as ColorState;
122+
if (colors == null) {
123+
base.EndBold (state);
124+
return;
125+
}
126+
127+
if (colors.BoldEnabled) {
128+
Console.ForegroundColor = Bold;
129+
Console.BackgroundColor = colors.Background;
130+
} else {
131+
Console.ForegroundColor = colors.Foreground;
132+
}
133+
colors.ItalicEnabled = false;
134+
}
135+
}
136+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace ApplicationUtility;
5+
6+
partial class MarkdownPresenter
7+
{
8+
class StringPresenter : BasePresenter
9+
{
10+
public override bool RendersToString => false;
11+
12+
StringBuilder builder = new ();
13+
14+
public override string AsString () => builder.ToString ();
15+
16+
public override void Append (string? text)
17+
{
18+
throw new NotImplementedException ();
19+
}
20+
21+
public override void Append (char ch)
22+
{
23+
throw new NotImplementedException ();
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)