using System.Linq; using System.Text; using ScriptCs.Contracts; namespace ScriptCs.ReplCommands { public class HelpCommand : IReplCommand { private readonly IConsole _console; public HelpCommand(IConsole console) { Guard.AgainstNullArgument("console", console); _console = console; } public string Description => "Shows this help."; public string CommandName => "help"; public object Execute(IRepl repl, object[] args) { Guard.AgainstNullArgument("repl", repl); _console.WriteLine("\nThe following commands are available in the REPL:"); foreach (var command in repl.Commands.OrderBy(x => x.Key)) { string key = string.Format(" :{0,-15} - ", command.Key); // make sure we have a good width for formatting purposes int descWidth = _console.Width - key.Length - 1; if (descWidth > 25) { _console.WriteLine(string.Format("{0}{1,10}", key, WrapTextToColumn(command.Value.Description, descWidth, indentWidth: key.Length))); } else { // safe-guard: just in the case we have a really long Repl Command "key" // and a really narrow console width don't wrap the description // note: the extra newline if to at least make somewhat readable _console.WriteLine(string.Format("{0}{1,10}\n", key, command.Value.Description)); } } _console.WriteLine(string.Empty); return null; } /// /// Word wrap text to specified column width. /// /// Unformatted text. /// Size of the column width. /// Indentation width when the text is wrap. The first line is not indented. /// First line indent width. /// Formatted text. /// In the future, I believe this method will be moved into some sort of formatting helper class. private string WrapTextToColumn(string text, int columnWidth, int indentWidth = 0, int initialWidth = 0) { // check the initial width if ((initialWidth < 0) || (initialWidth > (indentWidth + columnWidth))) { throw new System.ArgumentOutOfRangeException("initialWidth"); } // TODO: Add additional parameter error checking StringBuilder paragraph = new StringBuilder(text.Trim()); // add the initial space to text paragraph.Insert(0, " ", initialWidth); if (paragraph.Length > (columnWidth)) { int pos = columnWidth; int backSearchLimit = initialWidth; do { // find a whitespace we can wrap the description line int savedPos = pos; while (!char.IsWhiteSpace(paragraph[pos])) { pos--; // guard against not finding a natural whitespace // don't go below the spaces we create (indent) if (pos < backSearchLimit) { pos = savedPos; break; } } if (char.IsWhiteSpace(paragraph[pos])) { paragraph.Remove(pos, 1); // remove the whitespace we found } // inject a newline paragraph.Insert(pos, System.Environment.NewLine); pos += System.Environment.NewLine.Length; paragraph.Insert(pos, " ", indentWidth); pos += indentWidth; // prevent searching for whitespace to go below the spaces we put in backSearchLimit = pos; pos += columnWidth; } while (pos < paragraph.Length); } return paragraph.ToString(); } } }