Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(GH-181) Short confirm prompt / (GH-184) Show a prompt character #204

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -183,13 +183,13 @@ public void warn_when_admin_needs_elevation(ChocolateyConfiguration config)

if (shouldWarn)
{
var selection = InteractivePrompt.prompt_for_confirmation(@"
var selection = InteractivePrompt.prompt_for_confirmation_short(@"
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 @@ -199,5 +199,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 @@ -832,7 +832,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 @@ -234,16 +234,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
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