From fdd7a1b3a91a2e6089d3932a82aea542b3071956 Mon Sep 17 00:00:00 2001 From: Jocko Date: Mon, 5 Feb 2018 18:38:09 -0700 Subject: [PATCH] Added some new features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added the “bright” colors to the ANSI color constants Added the ability to specify a range in Shell#ask — This doesn’t convert to an array for the answer set, it just displays the min and max of the array. Added a `ask_if_nil` function to ask a question if the provided value is nil, otherwise return the value. — This helps with values that are required but may be nil at initialization of the task. --- lib/thor/shell/basic.rb | 56 +++++++++++++++++++++++++++++++++++++--- lib/thor/shell/color.rb | 38 ++++++++++++++++++++------- spec/shell/basic_spec.rb | 15 +++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/lib/thor/shell/basic.rb b/lib/thor/shell/basic.rb index 7430cd42f..5b335d2a5 100644 --- a/lib/thor/shell/basic.rb +++ b/lib/thor/shell/basic.rb @@ -48,7 +48,7 @@ def indent(count = 1) # Asks something to the user and receives a response. # # If asked to limit the correct responses, you can pass in an - # array of acceptable answers. If one of those is not supplied, + # array or range of acceptable answers. If one of those is not supplied, # they will be shown a message stating that one of those answers # must be given and re-asked the question. # @@ -74,12 +74,48 @@ def ask(statement, *args) color = args.first if options[:limited_to] - ask_filtered(statement, color, options) + if options[:limited_to].is_a?(Range) + ask_range(statement, color, options) + else + ask_filtered(statement, color, options) + end else ask_simply(statement, color, options) end end + # Asks something to the user and receives a response, but only if the + # provided value is nil. + # + # If asked to limit the correct responses, you can pass in an + # array or range of acceptable answers. If one of those is not supplied, + # they will be shown a message stating that one of those answers + # must be given and re-asked the question. + # + # If asking for sensitive information, the :echo option can be set + # to false to mask user input from $stdin. + # + # If the required input is a path, then set the path option to + # true. This will enable tab completion for file paths relative + # to the current working directory on systems that support + # Readline. + # + # ==== Example + # ask("What is your name?") + # + # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) + # + # ask("How old are you?", :limited_to => (1...120)) + # + # ask("What is your password?", :echo => false) + # + # ask("Where should the file be saved?", :path => true) + # + def ask_if_nil(value, statement, *args) + return value unless value.nil? + ask(statement, args) + end + # Say (print) something to the user. If the sentence ends with a whitespace # or tab character, a new line is not appended (print + flush). Otherwise # are passed straight to puts (behavior got from Highline). @@ -423,9 +459,21 @@ def ask_simply(statement, color, options) end end + def ask_range(statement, color, options) + answer_set = options[:limited_to].to_a + correct_answer = nil + until correct_answer + answers = "#{answer_set.min}-#{answer_set.max}" + answer = ask_simply("#{statement} [#{answers}]", color, options) + correct_answer = answer_set.map{ |a| a.to_s }.include?(answer) ? answer : nil + say("Your response must be in the range: [#{answers}]. Please try again.") unless correct_answer + end + correct_answer + end + def ask_filtered(statement, color, options) - answer_set = options[:limited_to] - correct_answer = nil + answer_set = options[:limited_to] + correct_answer = nil until correct_answer answers = answer_set.join(", ") answer = ask_simply("#{statement} [#{answers}]", color, options) diff --git a/lib/thor/shell/color.rb b/lib/thor/shell/color.rb index 2a90d46b8..93abf53b7 100644 --- a/lib/thor/shell/color.rb +++ b/lib/thor/shell/color.rb @@ -7,26 +7,44 @@ module Shell # class Color < Basic # Embed in a String to clear all previous ANSI sequences. - CLEAR = "\e[0m" + CLEAR = "\e[0m" # The start of an ANSI bold sequence. - BOLD = "\e[1m" + BOLD = "\e[1m" # Set the terminal's foreground ANSI color to black. - BLACK = "\e[30m" + BLACK = "\e[30m" + # Set the terminal's foreground ANSI color to black. + BRIGHT_BLACK = "\e[30;1m" + # Set the terminal's foreground ANSI color to red. + RED = "\e[31m" # Set the terminal's foreground ANSI color to red. - RED = "\e[31m" + BRIGHT_RED = "\e[31;1m" + # Set the terminal's foreground ANSI color to green. + GREEN = "\e[32m" # Set the terminal's foreground ANSI color to green. - GREEN = "\e[32m" + BRIGHT_GREEN = "\e[32;1m" # Set the terminal's foreground ANSI color to yellow. - YELLOW = "\e[33m" + YELLOW = "\e[33m" + # Set the terminal's foreground ANSI color to yellow. + BRIGHT_YELLOW = "\e[33;1m" + # Set the terminal's foreground ANSI color to blue. + BLUE = "\e[34m" # Set the terminal's foreground ANSI color to blue. - BLUE = "\e[34m" + BRIGHT_BLUE = "\e[34;1m" # Set the terminal's foreground ANSI color to magenta. - MAGENTA = "\e[35m" + MAGENTA = "\e[35m" + # Set the terminal's foreground ANSI color to magenta. + BRIGHT_MAGENTA = "\e[35;1m" + # Set the terminal's foreground ANSI color to cyan. + CYAN = "\e[36m" # Set the terminal's foreground ANSI color to cyan. - CYAN = "\e[36m" + BRIGHT_CYAN = "\e[36;1m" + # Set the terminal's foreground ANSI color to white. + WHITE = "\e[37m" # Set the terminal's foreground ANSI color to white. - WHITE = "\e[37m" + BRIGHT_WHITE = "\e[37;1m" + + # Set the terminal's background ANSI color to black. ON_BLACK = "\e[40m" diff --git a/spec/shell/basic_spec.rb b/spec/shell/basic_spec.rb index 8fa1052e5..bfcc2a978 100644 --- a/spec/shell/basic_spec.rb +++ b/spec/shell/basic_spec.rb @@ -81,6 +81,14 @@ def shell expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end + it "prints a message to the user with the available range and reasks the question after an incorrect response" do + ages = (1..120) + expect($stdout).to receive(:print).with("Your response must be in the range: [1-120]. Please try again.\n") + expect(Thor::LineEditor).to receive(:readline).with('How old are you? [1-120] ', :limited_to => ages).and_return("150", "20") + expect(shell.ask('How old are you?', :limited_to => ages)).to eq("20") + end + + it "prints a message to the user containing a default and sets the default if only enter is pressed" do expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? (vanilla) ', :default => "vanilla").and_return("") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :default => "vanilla")).to eq("vanilla") @@ -92,6 +100,13 @@ def shell expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ', :default => "vanilla", :limited_to => flavors).and_return("moose tracks", "") expect(shell.ask("What's your favorite Neopolitan flavor?", :default => "vanilla", :limited_to => flavors)).to eq("vanilla") end + + it "prints a message to the user with the available range and reasks the question after an incorrect response and then returns the default" do + ages = (1..120) + expect($stdout).to receive(:print).with("Your response must be in the range: [1-120]. Please try again.\n") + expect(Thor::LineEditor).to receive(:readline).with('How old are you? [1-120] (20) ', :default => "20", :limited_to => ages).and_return("150", "") + expect(shell.ask("How old are you?", :default => "20", :limited_to => ages)).to eq("20") + end end describe "#yes?" do