Skip to content

Commit

Permalink
(chocolateyGH-181) Short prompt + (chocolateyGH-184) Prompt character
Browse files Browse the repository at this point in the history
  • Loading branch information
christianrondeau committed Jul 22, 2015
1 parent c30b3a1 commit 9279ad3
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -437,5 +437,208 @@ public void should_error_when_any_choice_not_available_is_given()
console.Verify(c => c.ReadLine(), Times.AtLeast(8));
}
}

public class when_prompting_short_with_interactivePrompt_guard_errors : InteractivePromptSpecsBase
{
private Func<string> prompt;

public override void Because()
{
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
prompt = () => InteractivePrompt.prompt_for_confirmation_short(prompt_value, choices);
}

[Fact]
public void should_error_when_the_choicelist_is_null()
{
choices = null;
bool errored = false;
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception)
{
errored = true;
}

errored.ShouldBeTrue();
console.Verify(c => c.ReadLine(), Times.Never);
}

[Fact]
public void should_error_when_the_choicelist_is_empty()
{
choices = new List<string>();
bool errored = false;
string errorMessage = string.Empty;
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception ex)
{
errored = true;
errorMessage = ex.Message;
}

errored.ShouldBeTrue();
errorMessage.ShouldContain("No choices passed in.");
console.Verify(c => c.ReadLine(), Times.Never);
}

[Fact]
public void should_error_when_the_prompt_input_is_null()
{
choices = new List<string> { "bob" };
prompt_value = null;
bool errored = false;
string errorMessage = string.Empty;
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception ex)
{
errored = true;
errorMessage = ex.Message;
}

errored.ShouldBeTrue();
errorMessage.ShouldContain("Value for prompt cannot be null.");
console.Verify(c => c.ReadLine(), Times.Never);
}

[Fact]
public void should_error_when_the_choicelist_contains_empty_values()
{
choices = new List<string> { "bob", "" };
bool errored = false;
string errorMessage = string.Empty;
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception ex)
{
errored = true;
errorMessage = ex.Message;
}

errored.ShouldBeTrue();
errorMessage.ShouldContain("Some choices are empty.");
console.Verify(c => c.ReadLine(), Times.Never);
}

[Fact]
public void should_error_when_the_choicelist_has_multiple_items_with_same_first_letter()
{
choices = new List<string> {"sally", "suzy"};
bool errored = false;
string errorMessage = string.Empty;
console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception ex)
{
errored = true;
errorMessage = ex.Message;
}

errored.ShouldBeTrue();
errorMessage.ShouldContain("Multiple choices have the same first letter.");
console.Verify(c => c.ReadLine(), Times.Never);
}
}

public class when_prompting_short_with_interactivePrompt : InteractivePromptSpecsBase
{
private Func<string> prompt;

public override void Because()
{
prompt = () => InteractivePrompt.prompt_for_confirmation_short(prompt_value, choices);
}

public override void AfterObservations()
{
base.AfterObservations();
should_have_called_Console_ReadLine();
}

[Fact]
public void should_error_when_no_answer_given()
{
bool errored = false;

console.Setup(c => c.ReadLine()).Returns(""); //Enter pressed
try
{
prompt();
}
catch (Exception)
{
errored = true;
}
errored.ShouldBeTrue();
console.Verify(c => c.ReadLine(), Times.AtLeast(8));
}

[Fact]
public void should_return_yes_when_yes_is_given()
{
console.Setup(c => c.ReadLine()).Returns("yes");
var result = prompt();
result.ShouldEqual("yes");
}

[Fact]
public void should_return_yes_when_y_is_given()
{
console.Setup(c => c.ReadLine()).Returns("y");
var result = prompt();
result.ShouldEqual("yes");
}

[Fact]
public void should_return_no_choice_when_no_is_given()
{
console.Setup(c => c.ReadLine()).Returns("no");
var result = prompt();
result.ShouldEqual("no");
}

[Fact]
public void should_return_no_choice_when_n_is_given()
{
console.Setup(c => c.ReadLine()).Returns("n");
var result = prompt();
result.ShouldEqual("no");
}

[Fact]
public void should_error_when_any_choice_not_available_is_given()
{
bool errored = false;

console.Setup(c => c.ReadLine()).Returns("yup"); //Enter pressed
try
{
prompt();
}
catch (Exception)
{
errored = true;
}
errored.ShouldBeTrue();
console.Verify(c => c.ReadLine(), Times.AtLeast(8));
}
}
}
}
8 changes: 4 additions & 4 deletions src/chocolatey/infrastructure.app/runners/GenericRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,14 @@ public void warn_when_admin_needs_elevation(ChocolateyConfiguration config)

if (!config.Information.IsProcessElevated && config.Information.IsUserAdministrator)
{
var selection = InteractivePrompt.prompt_for_confirmation(@"
var selection = InteractivePrompt.prompt_for_confirmation_short(@"
Chocolatey detected you are not running from an elevated command shell
(cmd/powershell). You may experience errors - many functions/packages
require admin rights. Only advanced users should run choco w/out an
elevated shell. When you open the command shell, you should ensure
that you do so with ""Run as Administrator"" selected.
Do you want to continue?", new[] { "yes", "no" }, defaultChoice: null, requireAnswer: true);
Do you want to continue?", new[] { "yes", "no" });

if (selection.is_equal_to("no"))
{
Expand All @@ -174,5 +174,5 @@ require admin rights. Only advanced users should run choco w/out an
}

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void run(PackageResult packageResult, ChocolateyConfiguration config)
var skipUninstaller = true;
if (config.PromptForConfirmation)
{
var selection = InteractivePrompt.prompt_for_confirmation("Uninstall may not be silent (could not detect). Proceed?", new[] {"yes", "no"}, defaultChoice: null, requireAnswer: true);
var selection = InteractivePrompt.prompt_for_confirmation_short("Uninstall may not be silent (could not detect). Proceed?", new[] {"yes", "no"});
if (selection.is_equal_to("yes")) skipUninstaller = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ private void rollback_previous_version(ChocolateyConfiguration config, PackageRe
var rollback = true;
if (config.PromptForConfirmation)
{
var selection = InteractivePrompt.prompt_for_confirmation(" Unsuccessful operation for {0}.{1} Do you want to rollback to previous version (package files only)?".format_with(packageResult.Name, Environment.NewLine), new[] { "yes", "no" }, defaultChoice: null, requireAnswer: true);
var selection = InteractivePrompt.prompt_for_confirmation_short(" Unsuccessful operation for {0}.{1} Do you want to rollback to previous version (package files only)?".format_with(packageResult.Name, Environment.NewLine), new[] { "yes", "no" });
if (selection.is_equal_to("no")) rollback = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,17 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack
this.Log().Info(ChocolateyLoggers.Important, () => @"Note: To confirm automatically next time, use '-y' or consider setting
'allowGlobalConfirmation'. Run 'choco feature -h' for more details.");

var selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run the script?", new[] {"yes", "no", "print"}, defaultChoice: null, requireAnswer: true);
var selection = InteractivePrompt.prompt_for_confirmation_short(@"Do you want to run the script?", new[] {"yes", "no", "print"});

if (selection.is_equal_to("print"))
{
this.Log().Info(ChocolateyLoggers.Important, "------ BEGIN SCRIPT ------");
this.Log().Info(() => "{0}{1}{0}".format_with(Environment.NewLine, chocoPowerShellScriptContents.escape_curly_braces()));
this.Log().Info(ChocolateyLoggers.Important, "------- END SCRIPT -------");
selection = InteractivePrompt.prompt_for_confirmation(@"Do you want to run this script?", new[] { "yes", "no" }, defaultChoice: null, requireAnswer: true);
selection = InteractivePrompt.prompt_for_confirmation_short(@"Do you want to run this script?", new[] { "yes", "no" });
}


if (selection.is_equal_to("yes")) shouldRun = true;
if (selection.is_equal_to("no"))
{
Expand Down
5 changes: 5 additions & 0 deletions src/chocolatey/infrastructure/adapters/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ namespace chocolatey.infrastructure.adapters

public sealed class Console : IConsole
{
public void Write(string value)
{
System.Console.Write(value);
}

public string ReadLine()
{
return System.Console.ReadLine();
Expand Down
28 changes: 4 additions & 24 deletions src/chocolatey/infrastructure/adapters/IConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,13 @@ namespace chocolatey.infrastructure.adapters

// ReSharper disable InconsistentNaming

/// <summary>
/// Adapter for <see cref="System.Console"/>
/// </summary>
public interface IConsole
{
/// <summary>
/// Reads the next line of characters from the standard input stream.
/// </summary>
/// <returns>
/// The next line of characters from the input stream, or null if no more lines are available.
/// </returns>
/// <exception cref="T:System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="T:System.OutOfMemoryException">
/// There is insufficient memory to allocate a buffer for the returned string.
/// </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// The number of characters in the next line of characters is greater than <see cref="F:System.Int32.MaxValue" />.
/// </exception>
/// <filterpriority>1</filterpriority>
void Write(string value);
string ReadLine();

/// <summary>
/// Gets the standard error output stream.
/// </summary>
/// <returns>
/// A <see cref="T:System.IO.TextWriter" /> that represents the standard error output stream.
/// </returns>
/// <filterpriority>1</filterpriority>
TextWriter Error { get; }
}

Expand Down
43 changes: 43 additions & 0 deletions src/chocolatey/infrastructure/commandline/InteractivePrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,48 @@ private static IConsole Console
get { return _console.Value; }
}

public static string prompt_for_confirmation_short(string prompt, IEnumerable<string> choices, int repeat = 10)
{
if (repeat < 0) throw new ApplicationException("Too many bad attempts. Stopping before application crash.");
Ensure.that(() => prompt).is_not_null();
Ensure.that(() => choices).is_not_null();
Ensure
.that(() => choices)
.meets(
c => c.Any(),
(name, value) => { throw new ApplicationException("No choices passed in. Please ensure you pass choices."); });
Ensure
.that(() => choices)
.meets(
c => !c.Any(String.IsNullOrWhiteSpace),
(name, value) => { throw new ApplicationException("Some choices are empty. Please ensure you provide no empty choices."); });
Ensure
.that(() => choices)
.meets(
c => c.Select(entry => entry.FirstOrDefault()).Distinct().Count() == c.Count(),
(name, value) => { throw new ApplicationException("Multiple choices have the same first letter. Please ensure you pass choices with different first letters."); });

var promptWithChoices = "{0} ({1}): ".format_with(prompt, String.Join("/", choices));

Console.Write(promptWithChoices);
var selection = Console.ReadLine();

"chocolatey".Log().Info(ChocolateyLoggers.LogFileOnly, "{0}{1}".format_with(promptWithChoices, selection));

// check to see if value was passed
foreach (var choice in choices)
{
if (choice.is_equal_to(selection) || choice.Substring(0, 1).is_equal_to(selection))
{
selection = choice;
return selection;
}
}

"chocolatey".Log().Error(ChocolateyLoggers.Important, "Your choice of '{0}' is not a valid selection.".format_with(selection));
return prompt_for_confirmation_short(prompt, choices, repeat - 1);
}

public static string prompt_for_confirmation(string prompt, IEnumerable<string> choices, string defaultChoice, bool requireAnswer, int repeat = 10)
{
if (repeat < 0) throw new ApplicationException("Too many bad attempts. Stopping before application crash.");
Expand Down Expand Up @@ -69,6 +111,7 @@ public static string prompt_for_confirmation(string prompt, IEnumerable<string>
counter++;
}

Console.Write("> ");
var selection = Console.ReadLine();

if (string.IsNullOrWhiteSpace(selection) && defaultChoice != null)
Expand Down
2 changes: 2 additions & 0 deletions src/chocolatey/infrastructure/logging/ChocolateyLoggers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public enum ChocolateyLoggers
Normal,
Verbose,
Important,
// Used to output prompt results in log file, but not in the console
LogFileOnly,
}
}
4 changes: 4 additions & 0 deletions src/chocolatey/infrastructure/logging/log4net.config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
<appender-ref ref="VerboseLoggingColoredConsoleAppender" />
</logger>

<logger name="LogFileOnly">
<level value="INFO" />
</logger>

<logger name="chocolatey">
<level value="DEBUG"/>
<appender-ref ref="NormalLoggingColoredConsoleAppender" />
Expand Down

0 comments on commit 9279ad3

Please sign in to comment.