Skip to content

Commit b98fd26

Browse files
committed
New markdown construction class. Rendering TBD.
1 parent 5479191 commit b98fd26

File tree

9 files changed

+327
-344
lines changed

9 files changed

+327
-344
lines changed

tools/apput/src/Markdown/MarkdownDocument.cs

Lines changed: 242 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,146 +4,294 @@
44

55
namespace ApplicationUtility;
66

7-
// TODO: abstract out StringBuilder into a class which will have the same API but will be able
8-
// to either write to a StringBuilder or render directly to the console, with color (if
9-
// supported)
107
class MarkdownDocument
118
{
12-
List<MarkdownElement> elements = new ();
9+
const int DefaultLineWidth = 100;
10+
11+
readonly int lineWidth;
12+
readonly StringBuilder doc = new ();
13+
readonly Stack<int> indent = new ();
1314

14-
public bool IsEmpty => elements.Count == 0;
15+
public string Text => doc.ToString ();
1516

16-
public MarkdownHeading AddHeading (uint level, string text)
17+
public MarkdownDocument (int lineWidth = DefaultLineWidth)
1718
{
18-
var heading = new MarkdownHeading (level, text);
19-
elements.Add (heading);
20-
return heading;
19+
this.lineWidth = lineWidth >= 0 ? lineWidth : DefaultLineWidth;
20+
ResetIndent ();
2121
}
2222

23-
public static MarkdownTextSpan CreateNewLine () => new MarkdownTextSpan (Environment.NewLine);
24-
public static MarkdownParagraph CreateParagraph () => new MarkdownParagraph ();
25-
public static MarkdownTextSpan CreateText (string text, bool bold = false) => new MarkdownTextSpan (text) { Bold = bold };
23+
public MarkdownDocument AddHeading (uint level, string text)
24+
{
25+
// Headings don't break on `lineWidth`...
26+
if (doc.Length > 0) {
27+
AddNewline ();
28+
}
29+
30+
// ...and they always start at column 0...
31+
doc.Append ('#', (int)(level == 0 ? 1 : level));
32+
doc.Append (' ');
33+
AppendText (text, breakLine: false);
34+
AddNewline ();
35+
36+
// ...and they reset the indent
37+
ResetIndent ();
2638

27-
public void AddNewLine ()
39+
return this;
40+
}
41+
42+
public MarkdownDocument AddText (string text, MarkdownTextStyle style = MarkdownTextStyle.Plain, bool addIndent = true)
2843
{
29-
elements.Add (CreateNewLine ());
44+
AppendText (text, style, breakLine: true, addIndent);
45+
return this;
3046
}
3147

32-
public MarkdownPresenter Render (bool toConsole, bool useColor, bool renderPlainText)
48+
public MarkdownDocument AddNewline (int count = 1)
3349
{
34-
var presenter = new MarkdownPresenter (toConsole, useColor, renderPlainText);
35-
if (IsEmpty) {
36-
return presenter;
50+
if (count < 1) {
51+
return this;
3752
}
3853

39-
foreach (MarkdownElement element in elements) {
40-
RenderSafe (presenter, element, renderPlainText);
54+
for (int i = 0; i < count; i++) {
55+
doc.AppendLine ();
4156
}
4257

43-
return presenter;
58+
return this;
4459
}
4560

46-
void RenderSafe (MarkdownPresenter presenter, MarkdownElement element, bool plain)
61+
int AppendIndent ()
4762
{
48-
try {
49-
Render (presenter, element, plain);
50-
} catch (Exception ex) {
51-
Log.Warning ($"Failed to render element {element}.", ex);
63+
int indent = GetIndent ();
64+
if (indent > 0) {
65+
doc.Append (' ', indent);
5266
}
67+
68+
return indent;
5369
}
5470

55-
void RenderChildren (MarkdownPresenter presenter, MarkdownContainerElement element, bool plain,
56-
Action<MarkdownElement>? beforeElement = null, Action<MarkdownElement>? afterElement = null)
71+
void AppendText (string text, MarkdownTextStyle style = MarkdownTextStyle.Plain, bool breakLine = true, bool addIndent = true)
5772
{
58-
if (element.Children == null || element.Children.Count == 0) {
73+
int indent;
74+
75+
if (addIndent) {
76+
indent = AppendIndent ();
77+
} else {
78+
indent = GetIndent ();
79+
}
80+
81+
if (!breakLine) {
82+
doc.Append (text);
5983
return;
6084
}
6185

62-
foreach (MarkdownElement child in element.Children) {
63-
beforeElement?.Invoke (child);
64-
RenderSafe (presenter, child, plain);
65-
afterElement?.Invoke (child);
86+
// TODO: implement breaking the line at the last whitespace character before maximum line width.
87+
// Indent is included in calculations.
88+
doc.Append (text);
89+
}
90+
91+
public MarkdownDocument BeginList ()
92+
{
93+
doc.AppendLine ();
94+
SetNewIndent (2);
95+
return this;
96+
}
97+
98+
public MarkdownDocument StartListItem (string? text = null, MarkdownTextStyle style = MarkdownTextStyle.Plain)
99+
{
100+
AppendIndent ();
101+
doc.Append ("* ");
102+
103+
if (!String.IsNullOrEmpty (text)) {
104+
AppendText (text, style, addIndent: false);
66105
}
106+
107+
return this;
67108
}
68109

69-
void Render (MarkdownPresenter presenter, MarkdownElement element, bool plain)
110+
public MarkdownDocument EndListItem (bool appendLine = true)
70111
{
71-
if (element is MarkdownTextSpan textSpan) {
72-
Render (presenter, textSpan, plain);
73-
} else if (element is MarkdownHeading section) {
74-
Render (presenter, section, plain);
75-
} else if (element is MarkdownParagraph para) {
76-
Render (presenter, para, plain);
77-
} else if (element is MarkdownList list) {
78-
Render (presenter, list, plain);
79-
} else {
80-
throw new InvalidOperationException ($"Internal error: Markdown element {element.GetType ()} not supported when rendering.");
112+
if (appendLine) {
113+
doc.AppendLine ();
81114
}
115+
return this;
82116
}
83117

84-
void Render (MarkdownPresenter presenter, MarkdownList list, bool plain)
118+
public MarkdownDocument AddListItem (string? text = null, MarkdownTextStyle style = MarkdownTextStyle.Plain, bool appendLine = true)
85119
{
86-
presenter.AddNewLine ();
87-
RenderChildren (
88-
presenter,
89-
list,
90-
plain,
91-
beforeElement: (MarkdownElement element) => {
92-
if (element is MarkdownList) {
93-
return;
94-
}
95-
presenter.Append (" * ");
96-
},
97-
afterElement: (MarkdownElement _) => presenter.AddNewLine ()
98-
);
99-
presenter.AddNewLine ();
100-
presenter.AddNewLine ();
120+
StartListItem (text, style);
121+
EndListItem (appendLine);
122+
return this;
101123
}
102124

103-
void Render (MarkdownPresenter presenter, MarkdownParagraph para, bool plain)
125+
public MarkdownDocument EndList ()
104126
{
105-
RenderChildren (presenter, para, plain);
106-
int newLines = para.GetNumberOfNewLinesNeeded ();
107-
if (newLines > 0) {
108-
for (int i = 0; i < newLines; i++) {
109-
presenter.AddNewLine ();
110-
}
111-
}
127+
RestorePreviousIndent ();
128+
return this;
112129
}
113130

114-
void Render (MarkdownPresenter presenter, MarkdownHeading section, bool plain)
131+
int GetIndent () => indent.Peek ();
132+
133+
int SetNewIndent (int delta = 2)
115134
{
116-
presenter.Append ('#', (int)section.Level);
117-
presenter.Append (' ');
118-
presenter.Append (section.Text);
119-
presenter.AddNewLine ();
120-
presenter.AddNewLine ();
135+
int newIndent = GetIndent () + delta;
136+
indent.Push (newIndent);
137+
return newIndent;
138+
}
121139

122-
RenderChildren (presenter, section, plain);
140+
int RestorePreviousIndent ()
141+
{
142+
indent.Pop ();
143+
return indent.Peek ();
123144
}
124145

125-
void Render (MarkdownPresenter presenter, MarkdownTextSpan textSpan, bool plain)
146+
void ResetIndent ()
126147
{
127-
if (textSpan.IsEmpty) {
128-
return;
129-
}
148+
indent.Clear ();
149+
indent.Push (0);
150+
}
151+
}
130152

131-
RenderSpan (textSpan);
132-
if (textSpan.Fragments == null) {
133-
return;
134-
}
153+
// FIXME: replace all of this with https://www.nuget.org/packages/Microsoft.PowerShell.MarkdownRender
154+
//
155+
// TODO: abstract out StringBuilder into a class which will have the same API but will be able
156+
// to either write to a StringBuilder or render directly to the console, with color (if
157+
// supported)
158+
// class MarkdownDocumentOld
159+
// {
160+
// List<MarkdownElement> elements = new ();
135161

136-
foreach (MarkdownTextSpan fragment in textSpan.Fragments) {
137-
RenderSpan (fragment);
138-
}
162+
// public bool IsEmpty => elements.Count == 0;
139163

140-
void RenderSpan (MarkdownTextSpan span)
141-
{
142-
string? text = span.RemoveTailWhitespace ? span.Text?.TrimEnd () : span.Text;
164+
// public MarkdownHeading AddHeading (uint level, string text)
165+
// {
166+
// var heading = new MarkdownHeading (level, text);
167+
// elements.Add (heading);
168+
// return heading;
169+
// }
143170

144-
presenter.Append (span.Text, span.Style);
145-
}
146-
}
171+
// public static MarkdownTextSpan CreateNewLine () => new MarkdownTextSpan (Environment.NewLine);
172+
// public static MarkdownParagraph CreateParagraph () => new MarkdownParagraph ();
173+
// public static MarkdownTextSpan CreateText (string text, bool bold = false) => new MarkdownTextSpan (text) { Bold = bold };
147174

148-
void AddNewLine (StringBuilder sb) => sb.Append (Environment.NewLine);
149-
}
175+
// public void AddNewLine ()
176+
// {
177+
// elements.Add (CreateNewLine ());
178+
// }
179+
180+
// public MarkdownPresenter Render (bool toConsole, bool useColor, bool renderPlainText)
181+
// {
182+
// var presenter = new MarkdownPresenter (toConsole, useColor, renderPlainText);
183+
// if (IsEmpty) {
184+
// return presenter;
185+
// }
186+
187+
// foreach (MarkdownElement element in elements) {
188+
// RenderSafe (presenter, element, renderPlainText);
189+
// }
190+
191+
// return presenter;
192+
// }
193+
194+
// void RenderSafe (MarkdownPresenter presenter, MarkdownElement element, bool plain)
195+
// {
196+
// try {
197+
// Render (presenter, element, plain);
198+
// } catch (Exception ex) {
199+
// Log.Warning ($"Failed to render element {element}.", ex);
200+
// }
201+
// }
202+
203+
// void RenderChildren (MarkdownPresenter presenter, MarkdownContainerElement element, bool plain,
204+
// Action<MarkdownElement>? beforeElement = null, Action<MarkdownElement>? afterElement = null)
205+
// {
206+
// if (element.Children == null || element.Children.Count == 0) {
207+
// return;
208+
// }
209+
210+
// foreach (MarkdownElement child in element.Children) {
211+
// beforeElement?.Invoke (child);
212+
// RenderSafe (presenter, child, plain);
213+
// afterElement?.Invoke (child);
214+
// }
215+
// }
216+
217+
// void Render (MarkdownPresenter presenter, MarkdownElement element, bool plain)
218+
// {
219+
// if (element is MarkdownTextSpan textSpan) {
220+
// Render (presenter, textSpan, plain);
221+
// } else if (element is MarkdownHeading section) {
222+
// Render (presenter, section, plain);
223+
// } else if (element is MarkdownParagraph para) {
224+
// Render (presenter, para, plain);
225+
// } else if (element is MarkdownList list) {
226+
// Render (presenter, list, plain);
227+
// } else {
228+
// throw new InvalidOperationException ($"Internal error: Markdown element {element.GetType ()} not supported when rendering.");
229+
// }
230+
// }
231+
232+
// void Render (MarkdownPresenter presenter, MarkdownList list, bool plain)
233+
// {
234+
// presenter.AddNewLine ();
235+
// RenderChildren (
236+
// presenter,
237+
// list,
238+
// plain,
239+
// beforeElement: (MarkdownElement element) => {
240+
// if (element is MarkdownList) {
241+
// return;
242+
// }
243+
// presenter.Append (" * ");
244+
// },
245+
// afterElement: (MarkdownElement _) => presenter.AddNewLine ()
246+
// );
247+
// presenter.AddNewLine ();
248+
// presenter.AddNewLine ();
249+
// }
250+
251+
// void Render (MarkdownPresenter presenter, MarkdownParagraph para, bool plain)
252+
// {
253+
// RenderChildren (presenter, para, plain);
254+
// int newLines = para.GetNumberOfNewLinesNeeded ();
255+
// if (newLines > 0) {
256+
// for (int i = 0; i < newLines; i++) {
257+
// presenter.AddNewLine ();
258+
// }
259+
// }
260+
// }
261+
262+
// void Render (MarkdownPresenter presenter, MarkdownHeading section, bool plain)
263+
// {
264+
// presenter.Append ('#', (int)section.Level);
265+
// presenter.Append (' ');
266+
// presenter.Append (section.Text);
267+
// presenter.AddNewLine ();
268+
// presenter.AddNewLine ();
269+
270+
// RenderChildren (presenter, section, plain);
271+
// }
272+
273+
// void Render (MarkdownPresenter presenter, MarkdownTextSpan textSpan, bool plain)
274+
// {
275+
// if (textSpan.IsEmpty) {
276+
// return;
277+
// }
278+
279+
// RenderSpan (textSpan);
280+
// if (textSpan.Fragments == null) {
281+
// return;
282+
// }
283+
284+
// foreach (MarkdownTextSpan fragment in textSpan.Fragments) {
285+
// RenderSpan (fragment);
286+
// }
287+
288+
// void RenderSpan (MarkdownTextSpan span)
289+
// {
290+
// string? text = span.RemoveTailWhitespace ? span.Text?.TrimEnd () : span.Text;
291+
292+
// presenter.Append (span.Text, span.Style);
293+
// }
294+
// }
295+
296+
// void AddNewLine (StringBuilder sb) => sb.Append (Environment.NewLine);
297+
// }

0 commit comments

Comments
 (0)