Skip to content

Commit eaab6df

Browse files
committed
Word wrap Help Command output of each command description
1 parent 6cd7693 commit eaab6df

6 files changed

Lines changed: 199 additions & 2 deletions

File tree

src/ScriptCs.Contracts/IConsole.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ public interface IConsole
1919
void ResetColor();
2020

2121
ConsoleColor ForegroundColor { get; set; }
22+
23+
int Width { get; }
2224
}
2325
}

src/ScriptCs.Core/ReplCommands/HelpCommand.cs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using System.Text;
23
using ScriptCs.Contracts;
34

45
namespace ScriptCs.ReplCommands
@@ -28,13 +29,94 @@ public object Execute(IRepl repl, object[] args)
2829
{
2930
Guard.AgainstNullArgument("repl", repl);
3031

31-
_console.WriteLine("The following commands are available in the REPL:");
32+
_console.WriteLine("\nThe following commands are available in the REPL:");
3233
foreach (var command in repl.Commands.OrderBy(x => x.Key))
3334
{
34-
_console.WriteLine(string.Format(":{0,-15}{1,10}", command.Key, command.Value.Description));
35+
string key = string.Format(" :{0,-15} - ", command.Key);
36+
37+
// make sure we have a good width for formatting purposes
38+
int descWidth = _console.Width - key.Length - 1;
39+
if (descWidth > 25)
40+
{
41+
_console.WriteLine(string.Format("{0}{1,10}", key, WrapTextToColumn(command.Value.Description, descWidth, indentWidth: key.Length)));
42+
}
43+
else
44+
{
45+
// safe-guard: just in the case we have a really long Repl Command "key"
46+
// and a really narrow console width don't wrap the description
47+
// note: the extra newline if to at least make somewhat readable
48+
_console.WriteLine(string.Format("{0}{1,10}\n", key, command.Value.Description));
49+
}
3550
}
51+
_console.WriteLine(string.Empty);
3652

3753
return null;
3854
}
55+
56+
/// <summary>
57+
/// Word wrap text to specified column width.
58+
/// </summary>
59+
/// <param name="text">Unformatted text.</param>
60+
/// <param name="columnWidth">Size of the column width.</param>
61+
/// <param name="indentWidth">Indentation width when the text is wrap. The first line is not indented.</param>
62+
/// <param name="initialWidth">First line indent width.</param>
63+
/// <returns>Formatted text.</returns>
64+
/// <remarks>In the future, I believe this method will be moved into some sort of formatting helper class.</remarks>
65+
private string WrapTextToColumn(string text, int columnWidth, int indentWidth = 0, int initialWidth = 0)
66+
{
67+
// check the initial width
68+
if ((initialWidth < 0) || (initialWidth > (indentWidth + columnWidth)))
69+
{
70+
throw new System.ArgumentOutOfRangeException("initialWidth");
71+
}
72+
73+
// TODO: Add additional parameter error checking
74+
75+
StringBuilder paragraph = new StringBuilder(text.Trim());
76+
77+
// add the initial space to text
78+
paragraph.Insert(0, " ", initialWidth);
79+
80+
if (paragraph.Length > (columnWidth))
81+
{
82+
int pos = columnWidth;
83+
int backSearchLimit = initialWidth;
84+
do
85+
{
86+
// find a whitespace we can wrap the description line
87+
int savedPos = pos;
88+
while (!char.IsWhiteSpace(paragraph[pos]))
89+
{
90+
pos--;
91+
92+
// guard against not finding a natural whitespace
93+
// don't go below the spaces we create (indent)
94+
if (pos < backSearchLimit)
95+
{
96+
pos = savedPos;
97+
break;
98+
}
99+
}
100+
101+
if (char.IsWhiteSpace(paragraph[pos]))
102+
{
103+
paragraph.Remove(pos, 1); // remove the whitespace we found
104+
}
105+
// inject a newline
106+
paragraph.Insert(pos, System.Environment.NewLine);
107+
pos += System.Environment.NewLine.Length;
108+
paragraph.Insert(pos, " ", indentWidth);
109+
pos += indentWidth;
110+
111+
// prevent searching for whitespace to go below the spaces we put in
112+
backSearchLimit = pos;
113+
114+
pos += columnWidth;
115+
} while (pos < paragraph.Length);
116+
117+
}
118+
119+
return paragraph.ToString();
120+
}
39121
}
40122
}

src/ScriptCs.Core/ReplCommands/ScriptPacksCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public string CommandName
2929

3030
public object Execute(IRepl repl, object[] args)
3131
{
32+
Guard.AgainstNullArgument("repl", repl);
3233
var packContexts = repl.ScriptPackSession.Contexts;
3334

3435
if (packContexts.IsNullOrEmpty())

src/ScriptCs.Core/StringExtensions.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
24

35
namespace ScriptCs
46
{
@@ -9,6 +11,106 @@ public static string DefineTrace(this string code)
911
return string.Format("#define TRACE{0}{1}", Environment.NewLine, code);
1012
}
1113

14+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
15+
/// <summary>
16+
/// Split string on whitespace, but keeps string with quotes together
17+
/// For example: :cd "\\Foo Bar"
18+
/// :cd
19+
/// "\\Foo Bar".
20+
/// </summary>
21+
/// <param name="argument">String with or without quotes.</param>
22+
/// <returns>Array of strings.</returns>
23+
public static string[] SplitQuoted(this string argument)
24+
{
25+
// This method doesn't need a Guard Against Null Argument
26+
if (string.IsNullOrWhiteSpace(argument))
27+
{
28+
return argument.Split(' ');
29+
}
30+
31+
// count the number of quotes and throw something is not even
32+
// the fastest way is to just loop thru the string
33+
// http://cc.davelozinski.com/c-sharp/fastest-way-to-check-if-a-string-occurs-within-a-string
34+
Func<string, int> quoteCounterFunc = delegate (string line)
35+
{
36+
int count = 0;
37+
for (int x = 0; x < line.Length; x++)
38+
{
39+
if (line[x] == '"')
40+
{
41+
count++;
42+
}
43+
}
44+
return count;
45+
};
46+
int quotes = quoteCounterFunc(argument);
47+
if ((quotes % 2) != 0)
48+
{
49+
throw new ArgumentException("String is missing a closing quote");
50+
}
51+
52+
List<string> list = new List<string>(argument.Split(' '));
53+
54+
// quoted string needs to be combine back together
55+
if (quotes > 0 && list.Count > 0)
56+
{
57+
Predicate<string> findQuoteFunc = delegate (string s) { return s.Contains("\""); };
58+
// create function to find string item with odd number of quotes
59+
Func<int, int> findOddQuotedItemFunc = delegate (int startingIndex) {
60+
if (startingIndex < list.Count)
61+
{
62+
do
63+
{
64+
int quickFind = list.FindIndex(startingIndex, findQuoteFunc);
65+
int quickCount = quoteCounterFunc(list[quickFind]);
66+
if ((quickCount % 2) != 0)
67+
{
68+
return quickFind;
69+
}
70+
// we didn't find the quoted line we are looking for
71+
startingIndex = quickFind + 1;
72+
} while (startingIndex < list.Count);
73+
}
74+
return -1;
75+
};
76+
77+
int index = 0;
78+
do
79+
{
80+
int start = findOddQuotedItemFunc(index);
81+
if (start > 0)
82+
{
83+
// we have to locate the next string with odd number of quotes
84+
int end = findOddQuotedItemFunc(start + 1);
85+
86+
string combined = string.Empty;
87+
for (int x = start; x <= end; x++)
88+
{
89+
// because we split on whitespace, we have to put it back when combining
90+
combined += list[x] + ' ';
91+
}
92+
list[start] = combined.TrimEnd(); // remove the extra whitespace that was added
93+
94+
// removed the other parts of the combined string from the list
95+
do
96+
{
97+
list.RemoveAt(end--); // from the bottom up
98+
} while (start < end);
99+
100+
// advance to next item in the adjusted list
101+
index = start + 1;
102+
}
103+
else
104+
{
105+
break;
106+
}
107+
108+
} while (index < list.Count);
109+
}
110+
111+
return list.ToArray();
112+
}
113+
12114
public static string UndefineTrace(this string code)
13115
{
14116
return string.Format("#undef TRACE{0}{1}", Environment.NewLine, code);

src/ScriptCs.Hosting/FileConsole.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ public ConsoleColor ForegroundColor
6363
set { _innerConsole.ForegroundColor = value; }
6464
}
6565

66+
public int Width
67+
{
68+
get { return int.MaxValue; }
69+
}
70+
6671
private void Append(string text)
6772
{
6873
using (var writer = new StreamWriter(_path, true))

src/ScriptCs.Hosting/ScriptConsole.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,10 @@ public ConsoleColor ForegroundColor
5757
get { return Console.ForegroundColor; }
5858
set { Console.ForegroundColor = value; }
5959
}
60+
61+
public int Width
62+
{
63+
get { return Console.BufferWidth; }
64+
}
6065
}
6166
}

0 commit comments

Comments
 (0)