public void TestEggsML() { var expectations = new List <Tuple <string, string, string> > { Tuple.Create("", "(0:)", ""), Tuple.Create("*bold*", "(1:*(1:))", "*bold*"), Tuple.Create("non-bold *bold* non-bold", "(3:,*(1:),)", "non-bold *bold* non-bold"), Tuple.Create("more ***bold*", "(2:,*(1:))", "more *bold*"), Tuple.Create("Nested |*tags*|", "(2:,|(1:*(1:)))", "Nested |*tags*|"), Tuple.Create("Nested |`|||tags|`| yeah!", "(3:,|(1:|(1:)),)", "Nested |`|||tags|`| yeah!"), Tuple.Create("Empty |`| tag!", "(3:,|(0:),)", "Empty |`| tag!"), Tuple.Create(@"Escaped ""http://www.google.com/"" URL", "(1:)", @"""Escaped http://www.google.com/ URL"""), Tuple.Create(@"""String """"string"""" string""", "(1:)", @"String """"string"""" string"), Tuple.Create("[[square]] [<[tag]>]", "(2:,[(1:<(1:[(1:))))", "[[square]] [<[tag]>]"), }; foreach (var tuple in expectations) { Func <EggsNode, string> recurse = null; recurse = node => (node is EggsTag ? ((EggsTag)node).Tag + "(" + ((EggsTag)node).Children.Count.ToString() + ":" + ((EggsTag)node).Children.Select(n => recurse(n)).JoinString(",") + ")" : ""); var eggs = EggsML.Parse(tuple.Item1); Assert.AreEqual(tuple.Item2, recurse(eggs)); Assert.AreEqual(tuple.Item3, eggs.ToString()); } Assert.Throws <ArgumentNullException>(() => EggsML.Parse(null)); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz] xyz")); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz [[[xyz")); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz```xyz")); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz*****xyz")); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz]]] xyz")); Assert.Throws <EggsMLParseException>(() => EggsML.Parse("xyz *xyz")); }
/// <summary>Logs a message to the console.</summary> public override void Log(uint verbosity, LogType type, string message) { if (message == null) { message = "<null>"; } lock (this) { if (VerbosityLimit[type] < verbosity) { return; } string fmtInfo, indent; GetFormattedStrings(out fmtInfo, out indent, verbosity, type); var prevCol = Console.ForegroundColor; var col = GetMessageTypeColor(type); TextWriter consoleStream = (type == LogType.Error && ErrorsToStdErr) ? Console.Error : Console.Out; int wrapWidth = WordWrap ? ConsoleUtil.WrapToWidth() : int.MaxValue; bool first = true; if (InterpretMessagesAsEggsML) { foreach (var line in EggsML.Parse(message).ToConsoleColoredStringWordWrap(wrapWidth - fmtInfo.Length)) { Console.ForegroundColor = col; consoleStream.Write(first ? fmtInfo : indent); first = false; ConsoleUtil.WriteLine(line, type == LogType.Error && ErrorsToStdErr); } } else { Console.ForegroundColor = col; foreach (var line in message.WordWrap(wrapWidth - fmtInfo.Length)) { consoleStream.Write(first ? fmtInfo : indent); first = false; consoleStream.WriteLine(line); } } if (first) { Console.ForegroundColor = col; consoleStream.WriteLine(fmtInfo); // don't completely skip blank messages } Console.ForegroundColor = prevCol; } }
/// <summary> /// Generates a sequence of <see cref="ConsoleColoredString"/>s from an EggsML parse tree by word-wrapping the /// output at a specified character width.</summary> /// <param name="wrapWidth"> /// The number of characters at which to word-wrap the output.</param> /// <param name="hangingIndent"> /// The number of spaces to add to each line except the first of each paragraph, thus creating a hanging /// indentation.</param> /// <returns> /// The sequence of <see cref="ConsoleColoredString"/>s generated from the EggsML parse tree.</returns> /// <remarks> /// <para> /// The following EggsML tags map to the following console colors:</para> /// <list type="bullet"> /// <item><description> /// <c>~</c> = black, or dark gray if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>/</c> = dark blue, or blue if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>$</c> = dark green, or green if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>&</c> = dark cyan, or cyan if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>_</c> = dark red, or red if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>%</c> = dark magenta, or magenta if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>^</c> = dark yellow, or yellow if inside a <c>*</c> tag</description></item> /// <item><description> /// <c>=</c> = dark gray (independent of <c>*</c> tag)</description></item></list> /// <para> /// Text which is not inside any of the above color tags defaults to light gray, or white if inside a <c>*</c> /// tag.</para> /// <para> /// Additionally, the <c>+</c> tag can be used to suppress word-wrapping within a certain stretch of text. In /// other words, the contents of a <c>+</c> tag are treated as if they were a single word. Use this in /// preference to U+00A0 (no-break space) as it is more explicit and more future-compatible in case /// hyphenation is ever implemented here.</para></remarks> public IEnumerable <ConsoleColoredString> ToConsoleColoredStringWordWrap(int wrapWidth, int hangingIndent = 0) { var results = new List <ConsoleColoredString> { ConsoleColoredString.Empty }; EggsML.WordWrap(this, ConsoleColor.Gray, wrapWidth, (color, text) => text.Length, (color, text, width) => { results[results.Count - 1] += new ConsoleColoredString(text, color); }, (color, newParagraph, indent) => { var s = newParagraph ? 0 : indent + hangingIndent; results.Add(new ConsoleColoredString(new string(' ', s), color)); return(s); }, (color, tag, parameter) => { bool curLight = color >= ConsoleColor.DarkGray; switch (tag) { case '~': color = curLight ? ConsoleColor.DarkGray : ConsoleColor.Black; break; case '/': color = curLight ? ConsoleColor.Blue : ConsoleColor.DarkBlue; break; case '$': color = curLight ? ConsoleColor.Green : ConsoleColor.DarkGreen; break; case '&': color = curLight ? ConsoleColor.Cyan : ConsoleColor.DarkCyan; break; case '_': color = curLight ? ConsoleColor.Red : ConsoleColor.DarkRed; break; case '%': color = curLight ? ConsoleColor.Magenta : ConsoleColor.DarkMagenta; break; case '^': color = curLight ? ConsoleColor.Yellow : ConsoleColor.DarkYellow; break; case '=': color = ConsoleColor.DarkGray; break; case '*': color = curLight ? color : (ConsoleColor)((int)color + 8); break; } return(color, 0); }); if (results.Last().Length == 0) { results.RemoveAt(results.Count - 1); } return(results); }
/// <summary> /// Reconstructs the original EggsML that is represented by this node.</summary> /// <remarks> /// This does not necessarily return the same EggsML that was originally parsed. For example, redundant uses of /// the <c>`</c> character are removed.</remarks> public override string ToString() { if (_children.Count == 0) { return(Tag == null ? "" : EggsML.alwaysOpens(Tag) ? Tag.ToString() + EggsML.opposite(Tag) : Tag + "`" + Tag); } var childrenStr = stringify(_children, Tag); return(Tag == null ? childrenStr : childrenStr.StartsWith(Tag) ? childrenStr.EndsWith(EggsML.opposite(Tag)) ? Tag + "`" + childrenStr + "`" + EggsML.opposite(Tag) : Tag + "`" + childrenStr + EggsML.opposite(Tag) : childrenStr.EndsWith(EggsML.opposite(Tag)) ? Tag + childrenStr + "`" + EggsML.opposite(Tag) : Tag + childrenStr + EggsML.opposite(Tag)); }
/// <summary> /// Turns a list of child nodes into EggsML mark-up.</summary> /// <param name="children"> /// List of children to turn into mark-up.</param> /// <param name="tag"> /// If non-null, assumes we are directly inside a tag with the specified character, causing necessary escaping to /// be performed.</param> /// <returns> /// EggsML mark-up representing the same tree structure as this node.</returns> protected static string stringify(List <EggsNode> children, char?tag) { if (children == null || children.Count == 0) { return(""); } var sb = new StringBuilder(); for (int i = 0; i < children.Count; i++) { var childStr = children[i].ToString(); if (string.IsNullOrEmpty(childStr)) { continue; } // If the item is a tag, and it is the same tag character as the current one, we need to escape it by tripling it if (sb.Length > 0 && childStr.Length > 0 && childStr[0] == sb[sb.Length - 1]) { sb.Append('`'); } if (tag != null && children[i] is EggsTag && ((EggsTag)children[i]).Tag == tag && !EggsML.alwaysOpens(tag)) { sb.Append(new string(tag.Value, 2)); } sb.Append(childStr); } return(sb.ToString()); }