Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@krowe
Last active December 10, 2021 03:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krowe/756d334d7e29113f4f466e8cc9269a7c to your computer and use it in GitHub Desktop.
Save krowe/756d334d7e29113f4f466e8cc9269a7c to your computer and use it in GitHub Desktop.
Konsole Library - This is a single C# file which provides many common functions for formatting console output.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace BuildSite {
public class Kolor {
public ConsoleColor Foreground { get; set; } = ConsoleColor.Gray;
public ConsoleColor Background { get; set; } = ConsoleColor.Black;
public Kolor() { Set();}
public Kolor(ConsoleColor foreground, ConsoleColor background=ConsoleColor.Black) => Set(foreground, background);
public Kolor(Kolor clone) => Set(clone.Foreground, clone.Background);
public void Set(ConsoleColor foreground=ConsoleColor.Gray, ConsoleColor background=ConsoleColor.Black) { Foreground=foreground; Background=background; }
public void ApplyToKonsole() {
if(Foreground!=Console.ForegroundColor) Console.ForegroundColor=Foreground;
if(Background!=Console.BackgroundColor) Console.BackgroundColor=Background;
}
public static Kolor Current { get { return new Kolor(Console.ForegroundColor, Console.BackgroundColor); } }
public static Kolor Normal { get => new Kolor(); }
public static Kolor Bold { get => new Kolor(ConsoleColor.White); }
public static Kolor Small { get => new Kolor(ConsoleColor.DarkGray); }
public static Kolor Head { get => new Kolor(ConsoleColor.Yellow); }
public static Kolor Lines { get => new Kolor(ConsoleColor.White); }
public static Kolor DefinitionTerm { get => new Kolor(ConsoleColor.DarkGreen); }
public static Kolor DefinitionData { get => new Kolor(ConsoleColor.White); }
public static Kolor Info { get => new Kolor(ConsoleColor.DarkCyan); }
public static Kolor Msg { get => new Kolor(ConsoleColor.Cyan); }
public static Kolor Warn { get => new Kolor(ConsoleColor.DarkRed); }
public static Kolor Error { get => new Kolor(ConsoleColor.Red); }
public static Kolor InfoLabel { get => new Kolor(ConsoleColor.DarkYellow); }
public static Kolor MsgLabel { get => new Kolor(ConsoleColor.Yellow); }
public static Kolor WarnLabel { get => new Kolor(ConsoleColor.DarkYellow); }
public static Kolor ErrorLabel { get => new Kolor(ConsoleColor.Yellow); }
public static Kolor ProgressBar { get => new Kolor(ConsoleColor.DarkGray); }
public static Kolor ProgressBarComplete { get => new Kolor(ConsoleColor.Blue); }
public static Kolor ProgressBarBracket { get => new Kolor(ConsoleColor.Yellow); }
public static Kolor ProgressBarBracketComplete { get => new Kolor(ConsoleColor.DarkGreen); }
} // END class Kolor
public class Lokation {
int _X;
int _Y;
public int X { get=>_X; set=>_X=value; }
public int Y { get=>_Y; set=>_Y=value; }
public int Column { get=>_X; set=>_X=value; }
public int Row { get=>_Y; set=>_Y=value; }
public Lokation() => Set();
public Lokation(int column, int row) => Set(column,row);
public Lokation(Lokation clone) => Set(clone);
public void Set(int x=0, int y=0) { X=x; Y=y; }
public void Set(Lokation clone) { X=clone.X; Y=clone.Y; }
public override string ToString() { return String.Format("{0},{1}", X, Y); }
public void ApplyToKonsoleCursor() { Console.CursorLeft=Column; Console.CursorTop=Row; }
public static Lokation Current { get { return new Lokation(Console.CursorLeft, Console.CursorTop); } }
} // END class Lokation
public class Konsole {
public const int TabWidth = 8;
public static string NewLine { get=>Console.Out.NewLine; set=>Console.Out.NewLine=Console.Error.NewLine=value; }
public static string NewLineReplacement = " #[!NEWLINE!]# ";
#region Console Methods
public static void Write(string msg=null, Kolor color=null) {
if(String.IsNullOrEmpty(msg)) return;
Kolor oldColor=Kolor.Current;
if(color==null) color=Kolor.Normal;
color.ApplyToKonsole();
Console.Write(msg);
oldColor.ApplyToKonsole();
}
public static void Write(Lokation location, string msg=null, Kolor color=null, int display_width=0, bool cursor_return=true) {
Lokation oldPosition=Lokation.Current;
int display_space_available=Console.BufferWidth-location.Column;
if(display_space_available<=0) return;
if(display_width<1) display_width=Console.BufferWidth;
if(display_width>display_space_available) display_width=display_space_available;
location.ApplyToKonsoleCursor();
if(msg==null) msg="";
int msg_space_avaliable=display_space_available-msg.Length;
if(msg_space_avaliable<0) msg=msg.Substring(0, -msg_space_avaliable);
else if(msg_space_avaliable>0) msg+=new string(' ', msg_space_avaliable);
Write(msg, color);
if(cursor_return) oldPosition.ApplyToKonsoleCursor();
}
public static void WriteLine(string msg=null, Kolor color=null, bool backcolor_fill=true) {
if(String.IsNullOrEmpty(msg)) { Console.WriteLine(); return; }
string[] outWords=msg.Replace("\r", " ").Replace("\n", NewLineReplacement).Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
if(outWords!=null&&outWords.Length>0) {
Kolor oldColor=Kolor.Current;
if(color==null) color=Kolor.Normal;
color.ApplyToKonsole();
int initialColumn=Console.CursorLeft; // We use this to automatically determine where the left column should be.
string word, wordSpacer, wordChunk, NewLineReplacementWord=NewLineReplacement.Trim();
int wrapWidth=Console.BufferWidth-initialColumn, testLineWidth, wordPos;
if(wrapWidth>3) { // We skip output altogether if we can't even display 3 characters wide.
for(int dx=0; dx<outWords.Length; dx++) {
word=outWords[dx];
if(String.IsNullOrEmpty(word)) continue;
wordSpacer=dx==outWords.Length-1? "": " ";
if(word==NewLineReplacementWord) {
Console.Write(new string(' ', Console.BufferWidth-Console.CursorLeft));
} else if(word.Length<=wrapWidth) { // The word is shorter or equal to wrapWidth, we'll wrap normally.
testLineWidth=Console.CursorLeft+word.Length;
if(testLineWidth==Console.BufferWidth) { // This just barely fits so do not add a spacer and just use the newline as your spacer.
if(dx==outWords.Length-1) Console.Write(word);
else {Console.Write(word); } // We still need an indent if this isn't the last word.
} else if(testLineWidth<Console.BufferWidth) { // Just display normally.
Console.Write(word+wordSpacer);
} else { // When added, this word makes the line longer than wrapWidth so we need to first wrap the line.
Console.Write(new string(' ', Console.BufferWidth-Console.CursorLeft)+word+wordSpacer);
}
} else { // The word is longer than wrapWidth, we'll wrap the word itself to wrapWidth cols wide.
wordPos=Console.BufferWidth-Console.CursorLeft;
wordChunk=word.Substring(0, wordPos); // Get the portion that it'll take to finish the current line.
Console.Write(wordChunk);
while(wordPos<word.Length) {
wordChunk=word.Substring(wordPos, Math.Min(wrapWidth, word.Length-wordPos)); // Get the portion that it'll take to finish the current line.
Console.Write(wordChunk);
wordPos+=wordChunk.Length;
if(wordPos<word.Length) { Console.WriteLine(); }
else Console.Write(wordSpacer);
}
}
}
if(Console.CursorLeft>0) Console.Write(new string(' ', Console.BufferWidth-Console.CursorLeft));
}
oldColor.ApplyToKonsole();
}
}
public static void Info(string msg) { Write("..", Kolor.Info); Write(" Info: ", Kolor.InfoLabel); Wrap(msg, Kolor.DefinitionData); }
public static void Msg(string msg) { Write("**", Kolor.Msg); Write(" Message: ", Kolor.MsgLabel); Wrap(msg, Kolor.DefinitionData); }
public static void Warn(string msg) { Write("##", Kolor.Warn); Write(" Warning: ", Kolor.WarnLabel); Wrap(msg, Kolor.DefinitionData); }
public static void Error(string msg) { Write("!!", Kolor.Error); Write(" Error: ", Kolor.ErrorLabel); Wrap(msg, Kolor.DefinitionData); }
public static void Define(string term, string description, int term_col_width=20, Kolor term_color=null, Kolor descr_color=null,
WrapOptions options=WrapOptions.FillToEol|WrapOptions.PreserveIndent|WrapOptions.IndentInBlack, WrapPadding pad=WrapPadding.None) {
string term_fmt="{0,"+term_col_width+"}: ";
if(term_color==null) term_color=Kolor.DefinitionTerm;
if(descr_color==null) descr_color=Kolor.DefinitionData;
Write(String.Format(term_fmt, ShortenString(term, term_col_width-2)), term_color);
Wrap(description, descr_color, options , pad);
}
public static void ProgressBar(decimal complete=0, int width=20, bool show_value=true) {
if(width>Console.BufferWidth-Console.CursorLeft) width=Console.BufferWidth-Console.CursorLeft;
if(width<5) width=5;
if(complete<0) complete=0; else if(complete>100) complete=100;
int ticks_complete=Convert.ToInt32(width*(Convert.ToDouble(complete)/100f)), ticks_remaining=width-ticks_complete;
if(ticks_complete>0) Write(new string('█', ticks_complete), Kolor.ProgressBarComplete);
if(ticks_remaining>0) Write(new string('▒', ticks_remaining), Kolor.ProgressBar);
if(show_value) Write(String.Format(" {0:0}%", complete));
}
public static void ProgressBar(Lokation location=null, decimal complete=0, int width=20, bool show_value=true, bool cursor_return=true) {
Lokation originalCursorLoc=Lokation.Current;
location.ApplyToKonsoleCursor();
ProgressBar(complete, width, show_value);
if(cursor_return) originalCursorLoc.ApplyToKonsoleCursor();
}
#endregion
#region Wrap Method
[Flags] public enum WrapOptions {None=0, FillToEol=1, PreserveIndent=2, IndentInBlack=4};
public static string WrapOptions_ToString(WrapOptions options) {
string ret="";
if((options&WrapOptions.FillToEol)>0) ret+=(ret.Length>0? ", ": "")+"FillToEol";
if((options&WrapOptions.PreserveIndent)>0) ret+=(ret.Length>0? ", ": "")+"PreserveIndent";
if((options&WrapOptions.IndentInBlack)>0) ret+=(ret.Length>0? ", ": "")+"IndentInBlack";
return ret.Length>0? ret: "None";
}
[Flags] public enum WrapPadding {None=0, Top=1, Left=2, Bottom=4, Right=8};
public static string WrapPadding_ToString(WrapPadding pad) {
string ret="";
if((pad&WrapPadding.Top)>0) ret+=(ret.Length>0? ", ": "")+"Top";
if((pad&WrapPadding.Left)>0) ret+=(ret.Length>0? ", ": "")+"Left";
if((pad&WrapPadding.Bottom)>0) ret+=(ret.Length>0? ", ": "")+"Bottom";
if((pad&WrapPadding.Right)>0) ret+=(ret.Length>0? ", ": "")+"Right";
return ret.Length>0? ret: "None";
}
public const WrapOptions Default_WrapOptions = WrapOptions.FillToEol|WrapOptions.PreserveIndent|WrapOptions.IndentInBlack;
public static void Wrap(string msg, Kolor color=null, WrapOptions options=Default_WrapOptions, WrapPadding pad=WrapPadding.None) {
string[] outWords=msg.Replace("\r", " ").Replace("\n", NewLineReplacement).Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
if(outWords!=null&&outWords.Length>0) {
bool preserveIndent=(options&WrapOptions.PreserveIndent)>0, fillToEol=(options&WrapOptions.FillToEol)>0, indentInBlack=(options&WrapOptions.IndentInBlack)>0,
padLeft=(pad&WrapPadding.Left)>0, padRight=(pad&WrapPadding.Right)>0,
finalDx=false;
Kolor oldColor=Kolor.Current;
if(color==null) color=Kolor.Normal;
color.ApplyToKonsole();
int initialColumn=Console.CursorLeft; // We use this to automatically determine where the left column should be.
string indent=preserveIndent? new string(' ', initialColumn): "", NewLineReplacementWord=NewLineReplacement.Trim(), word, wordChunk/*, wordSpacer*/;
int wrapWidth=preserveIndent? Console.BufferWidth-initialColumn: Console.BufferWidth, testLineWidth, wordPos;
if(padLeft) wrapWidth--;
if(padRight) wrapWidth--;
if(wrapWidth>3) { // We skip output altogether if we can't even display 3 characters wide.
if((pad&WrapPadding.Top)>0) {
_EolFill(fillToEol);
if(preserveIndent) _Wrap__Indent(indentInBlack, indent, color.Background);
}
if(padLeft) Console.Write(" ");
for(int dx=0; dx<outWords.Length; dx++) {
word=outWords[dx];
if(String.IsNullOrEmpty(word)) continue;
finalDx=dx==outWords.Length-1;
//wordSpacer=finalDx==false&&outWords[dx+1]!=NewLineReplacementWord? " ": "";
if(word==NewLineReplacementWord) {
_EolFill(fillToEol);
if(preserveIndent&&finalDx==false)
_Wrap__Indent(indentInBlack, indent, color.Background);
if(padLeft) Console.Write(" ");
} else if(word.Length<=wrapWidth) { // The word is shorter or equal to wrapWidth, we'll wrap normally.
testLineWidth=Console.CursorLeft+word.Length;
if(padRight) testLineWidth++;
if(testLineWidth<Console.BufferWidth) { // Just display normally.
if(Console.CursorLeft>initialColumn+(padLeft?1:0)) Console.Write(" ");
Console.Write(word);
if(Console.CursorLeft<=initialColumn) {
if(preserveIndent&&finalDx==false&&outWords[dx+1]!=NewLineReplacementWord)
_Wrap__Indent(indentInBlack, indent, color.Background);
if(padLeft) Console.Write(" ");
}
} else { // When added, this word makes the line longer than wrapWidth so we need to first wrap the line.
_EolFill(fillToEol);
if(preserveIndent) _Wrap__Indent(indentInBlack, indent, color.Background);
if(padLeft) Console.Write(" ");
Console.Write(word);
}
} else { // The word is longer than wrapWidth, we'll wrap the word itself to wrapWidth cols wide.
if(Console.CursorLeft>initialColumn) Console.Write(" ");
wordPos=Console.BufferWidth-Console.CursorLeft;
if(padRight) wordPos--;
wordChunk=word.Substring(0, wordPos); // Get the portion that it'll take to finish the current line.
Console.Write(wordChunk);
if(padRight) _EolFill(fillToEol);
if(preserveIndent) _Wrap__Indent(indentInBlack, indent, color.Background);
if(padLeft) Console.Write(" ");
while(wordPos<word.Length) {
wordChunk=word.Substring(wordPos, Math.Min(wrapWidth, word.Length-wordPos)); // Get the portion that it'll take to finish the current line.
wordPos+=wordChunk.Length;
Console.Write(wordChunk);
//if(Console.CursorLeft==Console.BufferWidth-1) _Wrap__EolFill(fillToEol);
if(wordPos<word.Length) {
if(padRight) _EolFill(fillToEol);
if(preserveIndent) _Wrap__Indent(indentInBlack, indent, color.Background);
if(padLeft) Console.Write(" ");
}
}
}
if(!preserveIndent) initialColumn=0; // If we aren't preserving the indent then we want to set this to zero so that we don't need to check the value of preserveIndent any more than needed in the loop.
}
if(Console.CursorLeft>0) _EolFill(fillToEol);
if((pad&WrapPadding.Bottom)>0) {
if(preserveIndent) _Wrap__Indent(indentInBlack, indent, color.Background);
_EolFill(fillToEol);
}
}
oldColor.ApplyToKonsole();
} else Console.WriteLine(); // If we were passed an empty string we should still output a newline in order to maintain consistency.
}
private static void _EolFill(bool fillToEol) {
if(fillToEol) Console.Write(new string(' ', Console.BufferWidth-Console.CursorLeft)); else Console.WriteLine();
}
private static void _Wrap__Indent(bool indentInBlack, string indent, ConsoleColor background) {
if(indentInBlack&&Console.BackgroundColor!=ConsoleColor.Black) {
Console.BackgroundColor=ConsoleColor.Black;
Console.Write(indent);
Console.BackgroundColor=background;
} else Console.Write(indent);
}
#endregion
#region Head Methods
[Flags] public enum HeadOptions {None=0, FillToEol=1, Center=2, CenterInBox=4, IndentInBlack=8, Underline=16, Overline=32};
public static string HeadOptions_ToString(HeadOptions options) {
string ret="";
if((options&HeadOptions.FillToEol)>0) ret+=(ret.Length>0? ", ": "")+"FillToEol";
if((options&HeadOptions.Center)>0) ret+=(ret.Length>0? ", ": "")+"Center";
if((options&HeadOptions.CenterInBox)>0) ret+=(ret.Length>0? ", ": "")+"CenterInBox";
if((options&HeadOptions.IndentInBlack)>0) ret+=(ret.Length>0? ", ": "")+"IndentInBlack";
if((options&HeadOptions.Underline)>0) ret+=(ret.Length>0? ", ": "")+"Underline";
if((options&HeadOptions.Overline)>0) ret+=(ret.Length>0? ", ": "")+"Overline";
return ret.Length>0? ret: "None";
}
[Flags] public enum HeadPadding {None=0, AboveOverline=1, Left=2, BelowUnderline=4, Right=8, BelowOverline=16, AboveUnderline=32};
public static string HeadPadding_ToString(HeadPadding pad) {
string ret="";
if((pad&HeadPadding.AboveOverline)>0) ret+=(ret.Length>0? ", ": "")+"AboveOverline";
if((pad&HeadPadding.Left)>0) ret+=(ret.Length>0? ", ": "")+"Left";
if((pad&HeadPadding.BelowUnderline)>0) ret+=(ret.Length>0? ", ": "")+"BelowUnderline";
if((pad&HeadPadding.Right)>0) ret+=(ret.Length>0? ", ": "")+"Right";
if((pad&HeadPadding.BelowOverline)>0) ret+=(ret.Length>0? ", ": "")+"BelowOverline";
if((pad&HeadPadding.AboveUnderline)>0) ret+=(ret.Length>0? ", ": "")+"AboveUnderline";
return ret.Length>0? ret: "None";
}
public static int Default_HeadExtendLines = 0;
public static string Default_HeadLineFormat = "-";
public static HeadOptions Default_HeadOptions = HeadOptions.Underline;
public static HeadPadding Default_HeadPadding = HeadPadding.None;
public static void Head(string msg, int extendLines=-1, string lineFormat=null,
Kolor color=null, HeadOptions? options=null, HeadPadding? pad=null,
Kolor line_color=null) {
if(String.IsNullOrWhiteSpace(msg)) return;
Kolor oldColor=Kolor.Current;
if(extendLines<0) extendLines=Default_HeadExtendLines;
if(String.IsNullOrEmpty(lineFormat)) lineFormat=Default_HeadLineFormat;
if(color==null) color=Kolor.Head;
if(options==null||options.HasValue==false) options=Default_HeadOptions;
if(pad==null||pad.HasValue==false) pad=Default_HeadPadding;
if(line_color==null) line_color=Kolor.Lines;
string[] lines = msg.Split(new[] { NewLine }, StringSplitOptions.None);
int longestLineLen=0;
foreach(string line in lines) if(line!=null&&line.Length>longestLineLen) longestLineLen=line.Length;
int lineLen=longestLineLen+(extendLines*2);
if(lineLen>Console.BufferWidth) lineLen=Console.BufferWidth;
string lineString=new StringBuilder().Insert(0, lineFormat, (lineLen/lineFormat.Length)).ToString().Substring(0, lineLen),
msgPad=extendLines>0? new string(' ', extendLines):"";
bool fillToEol=(options&HeadOptions.FillToEol)>0, centerHeading=(options&HeadOptions.Center)>0, indentInBlack=(options&HeadOptions.IndentInBlack)>0;
int startCol=centerHeading? ((Console.BufferWidth-lineLen)/2): 0;
string indent=new string(' ', startCol), thisLine;
line_color.ApplyToKonsole();
if((pad&HeadPadding.AboveOverline)>0) _EolFill(fillToEol);
if((options&HeadOptions.Overline)>0) {
_Wrap__Indent(indentInBlack, indent, line_color.Background);
if((pad&HeadPadding.Left)>0) Console.Write(" ");
Console.Write(lineString);
if((pad&HeadPadding.Right)>0) Console.Write(" ");
_EolFill(fillToEol);
}
if((pad&HeadPadding.BelowOverline)>0) {
_Wrap__Indent(indentInBlack, indent, line_color.Background);
if((pad&HeadPadding.Left)>0) Console.Write(" ");
Console.Write(msgPad+(new string(' ', longestLineLen))+msgPad);
if((pad&HeadPadding.Right)>0) Console.Write(" ");
_EolFill(fillToEol);
}
color.ApplyToKonsole();
foreach(string line in lines) {
_Wrap__Indent(indentInBlack, indent, line_color.Background);
if((pad&HeadPadding.Left)>0) Console.Write(" ");
thisLine=(options&HeadOptions.CenterInBox)>0?
(new string(' ',(longestLineLen-line.Length)/2)+line+new string(' ', (longestLineLen-line.Length)/2)):
(line+new string(' ', longestLineLen-line.Length));
if(thisLine.Length<longestLineLen) thisLine+=" ";
Console.Write(msgPad+thisLine+msgPad);
if((pad&HeadPadding.Right)>0) Console.Write(" ");
_EolFill(fillToEol);
}
line_color.ApplyToKonsole();
if((pad&HeadPadding.AboveUnderline)>0) {
_Wrap__Indent(indentInBlack, indent, line_color.Background);
if((pad&HeadPadding.Left)>0) Console.Write(" ");
Console.Write(msgPad+(new string(' ', longestLineLen))+msgPad);
if((pad&HeadPadding.Right)>0) Console.Write(" ");
_EolFill(fillToEol);
}
if((options&HeadOptions.Underline)>0) {
_Wrap__Indent(indentInBlack, indent, line_color.Background);
if((pad&HeadPadding.Left)>0) Console.Write(" ");
Console.Write(lineString);
if((pad&HeadPadding.Right)>0) Console.Write(" ");
_EolFill(fillToEol);
}
if((pad&HeadPadding.BelowUnderline)>0) _EolFill(fillToEol);
oldColor.ApplyToKonsole();
}
#endregion
#region Helper Methods
public static string ShortenString(string str, int max_len=30) {
if(String.IsNullOrEmpty(str)) return "";
if(str.Length<=max_len) return str;
return str.Substring(0, max_len-3)+"...";
}
#endregion
} // END class ConsoleUtility
} // END namespace BuildSite
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment