From 7fc6f10320165ddf6ba50e59629e0d7a8a0632ea Mon Sep 17 00:00:00 2001 From: Ben Brady Date: Mon, 30 Nov 2015 16:28:52 -0800 Subject: [PATCH] namespacing - fixes arlimus/inquirer.rb#14 --- lib/inquirer.rb | 23 +-- lib/inquirer/prompts/checkbox.rb | 247 +++++++++++++++--------------- lib/inquirer/prompts/confirm.rb | 213 +++++++++++++------------- lib/inquirer/prompts/input.rb | 253 ++++++++++++++++--------------- lib/inquirer/prompts/list.rb | 225 ++++++++++++++------------- lib/inquirer/utils/iohelper.rb | 218 +++++++++++++------------- test/classes/checkbox_spec.rb | 4 +- test/classes/confirm_spec.rb | 4 +- test/classes/input_spec.rb | 4 +- test/classes/list_spec.rb | 4 +- test/minitest_helper.rb | 3 +- 11 files changed, 611 insertions(+), 587 deletions(-) diff --git a/lib/inquirer.rb b/lib/inquirer.rb index 023a2e9..7dd8ef0 100644 --- a/lib/inquirer.rb +++ b/lib/inquirer.rb @@ -1,23 +1,16 @@ require 'inquirer/version' require 'inquirer/utils/iohelper' -require 'inquirer/prompts/list' -require 'inquirer/prompts/checkbox' -require 'inquirer/prompts/input' -require 'inquirer/prompts/confirm' module Ask extend self - # implement prompts - def list *args, **kwargs - List.ask *args, **kwargs - end - def checkbox *args, **kwargs - Checkbox.ask *args, **kwargs + module Prompts + PROMPTS = [] end - def input *args, **kwargs - Input.ask *args, **kwargs - end - def confirm *args, **kwargs - Confirm.ask *args, **kwargs + Dir["#{File.dirname(__FILE__)}/inquirer/prompts/*.rb"].each{|f| require f} + # implement prompts + Prompts::PROMPTS.each do |prompt| + define_method(prompt) do |*args, **kwargs| + Prompts.const_get(prompt.capitalize).ask(*args, **kwargs) + end end end diff --git a/lib/inquirer/prompts/checkbox.rb b/lib/inquirer/prompts/checkbox.rb index 3878558..924d102 100644 --- a/lib/inquirer/prompts/checkbox.rb +++ b/lib/inquirer/prompts/checkbox.rb @@ -1,140 +1,147 @@ require 'term/ansicolor' require 'inquirer/style' -# Base rendering for simple lists -module CheckboxRenderer - def render heading = nil, list = [], footer = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the list - list.map do |li| - render_item li - end.join("") + - # render the footer - ( footer.nil? ? "" : @footer % footer ) - end - - def renderResponse heading = nil, response = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the footer - ( response.nil? ? "" : @response % response ) - end +module Ask + module Prompts + PROMPTS << :checkbox + + # Base rendering for simple lists + module CheckboxRenderer + def render heading = nil, list = [], footer = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the list + list.map do |li| + render_item li + end.join("") + + # render the footer + ( footer.nil? ? "" : @footer % footer ) + end - private + def renderResponse heading = nil, response = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the footer + ( response.nil? ? "" : @response % response ) + end - def render_item x - ( x["selected"] ? @selector : " " ) + - ( x["active"] ? @checkbox_on : @checkbox_off ) + - " " + - ( x["active"] ? @active_item : @item ) % x["value"] - end + private -end + def render_item x + ( x["selected"] ? @selector : " " ) + + ( x["active"] ? @checkbox_on : @checkbox_off ) + + " " + + ( x["active"] ? @active_item : @item ) % x["value"] + end -# Formatting for rendering -class CheckboxDefault - include CheckboxRenderer - C = Term::ANSIColor - def initialize( style ) - @heading = "%s:\n" - @footer = "%s\n" - @item = "%s\n" - @active_item = "%s" + "\n" - @selector = C.cyan style.selector - @checkbox_on = C.cyan style.checkbox_on - @checkbox_off = style.checkbox_off - end -end + end -# Default formatting for response -class CheckboxResponseDefault - include CheckboxRenderer - C = Term::ANSIColor - def initialize( style = nil ) - @heading = "%s: " - @response = C.cyan("%s") + "\n" - end -end + # Formatting for rendering + class CheckboxDefault + include CheckboxRenderer + C = Term::ANSIColor + def initialize( style ) + @heading = "%s:\n" + @footer = "%s\n" + @item = "%s\n" + @active_item = "%s" + "\n" + @selector = C.cyan style.selector + @checkbox_on = C.cyan style.checkbox_on + @checkbox_off = style.checkbox_off + end + end -class Checkbox - def initialize(question = nil, - elements = [], - default: nil, - renderer: nil, - responseRenderer: nil, - **kwargs) - @elements = elements - @question = question - @pos = 0 - @active = default || elements.map{|i| false} - @prompt = "" - @renderer = renderer || CheckboxDefault.new( Inquirer::Style::Default ) - @responseRenderer = responseRenderer = CheckboxResponseDefault.new() - end + # Default formatting for response + class CheckboxResponseDefault + include CheckboxRenderer + C = Term::ANSIColor + def initialize( style = nil ) + @heading = "%s: " + @response = C.cyan("%s") + "\n" + end + end - def update_prompt - # transform the list into - # {"value"=>..., "selected"=> true|false, "active"=> true|false } - e = @elements. - # attach the array position - map.with_index(0). - map do |c,pos| - { "value"=>c, "selected" => pos == @pos, "active" => @active[pos] } + class Checkbox + def initialize(question = nil, + elements = [], + default: nil, + renderer: nil, + responseRenderer: nil, + **kwargs) + @elements = elements + @question = question + @pos = 0 + @active = default || elements.map{|i| false} + @prompt = "" + @renderer = renderer || CheckboxDefault.new( Inquirer::Style::Default ) + @responseRenderer = responseRenderer = CheckboxResponseDefault.new() end - # call the renderer - @prompt = @renderer.render(@question, e) - end - def update_response - e = @elements - .map.with_index(0) - .select {|f, pos| @active[pos] } - .map {|f, pos| f } - @prompt = @responseRenderer.renderResponse(@question, e.join(", ")) - end + def update_prompt + # transform the list into + # {"value"=>..., "selected"=> true|false, "active"=> true|false } + e = @elements. + # attach the array position + map.with_index(0). + map do |c,pos| + { "value"=>c, "selected" => pos == @pos, "active" => @active[pos] } + end + # call the renderer + @prompt = @renderer.render(@question, e) + end - # Run the list selection, wait for the user to select an item and return - # the selected index - # Params: - # +clear+:: +Bool+ whether to clear the selection prompt once this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - # +response+:: +Bool+ whether show the rendered response when this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - def run clear, response - # finish if there's nothing to do - return @active if Array(@elements).empty? - - # hides the cursor while prompting - IOHelper.without_cursor do - # render the - IOHelper.render( update_prompt ) - # loop through user input - IOHelper.read_key_while do |key| - @pos = (@pos - 1) % @elements.length if key == "up" - @pos = (@pos + 1) % @elements.length if key == "down" - @active[@pos] = !@active[@pos] if key == "space" - IOHelper.rerender( update_prompt ) - # we are done if the user hits return - key != "return" + def update_response + e = @elements + .map.with_index(0) + .select {|f, pos| @active[pos] } + .map {|f, pos| f } + @prompt = @responseRenderer.renderResponse(@question, e.join(", ")) end - end - # clear the final prompt and the line - IOHelper.clear if clear + # Run the list selection, wait for the user to select an item and return + # the selected index + # Params: + # +clear+:: +Bool+ whether to clear the selection prompt once this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + # +response+:: +Bool+ whether show the rendered response when this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + def run clear, response + # finish if there's nothing to do + return @active if Array(@elements).empty? + + # hides the cursor while prompting + IOHelper.without_cursor do + # render the + IOHelper.render( update_prompt ) + # loop through user input + IOHelper.read_key_while do |key| + @pos = (@pos - 1) % @elements.length if key == "up" + @pos = (@pos + 1) % @elements.length if key == "down" + @active[@pos] = !@active[@pos] if key == "space" + IOHelper.rerender( update_prompt ) + # we are done if the user hits return + key != "return" + end + end + + # clear the final prompt and the line + IOHelper.clear if clear + + # show the answer + IOHelper.render( update_response ) if response + + # return the index of the selected item + @active + end - # show the answer - IOHelper.render( update_response ) if response + def self.ask question = nil, elements = [], **opts + l = Checkbox.new question, elements, **opts + l.run opts.fetch(:clear, true), opts.fetch(:response, true) + end - # return the index of the selected item - @active - end + end - def self.ask question = nil, elements = [], **opts - l = Checkbox.new question, elements, **opts - l.run opts.fetch(:clear, true), opts.fetch(:response, true) end - end diff --git a/lib/inquirer/prompts/confirm.rb b/lib/inquirer/prompts/confirm.rb index bad094b..75de590 100644 --- a/lib/inquirer/prompts/confirm.rb +++ b/lib/inquirer/prompts/confirm.rb @@ -1,118 +1,125 @@ require 'term/ansicolor' -# Base rendering for confirm -module ConfirmRenderer - def render heading = nil, default = nil, footer = nil - options = ['y','n'] - options[0].capitalize! if default == true - options[1].capitalize! if default == false - - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the defaults - @default % options + - # render the footer - ( footer.nil? ? "" : @footer % footer ) - end - - def renderResponse heading = nil, response = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the footer - ( response.nil? ? "" : @response % response ) - end -end - -# Default formatting for confirm rendering -class ConfirmDefault - include ConfirmRenderer - C = Term::ANSIColor - def initialize( style ) - @heading = "%s: " - @default = "(%s/%s)" - @footer = "%s" - end -end +module Ask + module Prompts + PROMPTS << :confirm + + # Base rendering for confirm + module ConfirmRenderer + def render heading = nil, default = nil, footer = nil + options = ['y','n'] + options[0].capitalize! if default == true + options[1].capitalize! if default == false + + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the defaults + @default % options + + # render the footer + ( footer.nil? ? "" : @footer % footer ) + end -# Default formatting for response -class ConfirmResponseDefault - include ConfirmRenderer - C = Term::ANSIColor - def initialize( style = nil ) - @heading = "%s: " - @response = C.cyan("%s") + "\n" - end -end + def renderResponse heading = nil, response = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the footer + ( response.nil? ? "" : @response % response ) + end + end -class Confirm - def initialize(question = nil, - default = nil, - renderer: nil, - responseRenderer: nil, - **kwargs) - @question = question - @value = "" - @default = default - @prompt = "" - @renderer = renderer || ConfirmDefault.new( Inquirer::Style::Default ) - @responseRenderer = responseRenderer = ConfirmResponseDefault.new() - end + # Default formatting for confirm rendering + class ConfirmDefault + include ConfirmRenderer + C = Term::ANSIColor + def initialize( style ) + @heading = "%s: " + @default = "(%s/%s)" + @footer = "%s" + end + end - def update_prompt - # call the renderer - @prompt = @renderer.render(@question, @default) - end + # Default formatting for response + class ConfirmResponseDefault + include ConfirmRenderer + C = Term::ANSIColor + def initialize( style = nil ) + @heading = "%s: " + @response = C.cyan("%s") + "\n" + end + end - def update_response - @prompt = @responseRenderer.renderResponse(@question, (@value)? 'Yes' : 'No') - end + class Confirm + def initialize(question = nil, + default = nil, + renderer: nil, + responseRenderer: nil, + **kwargs) + @question = question + @value = "" + @default = default + @prompt = "" + @renderer = renderer || ConfirmDefault.new( Inquirer::Style::Default ) + @responseRenderer = responseRenderer = ConfirmResponseDefault.new() + end - # Run the list selection, wait for the user to select an item and return - # the selected index - # Params: - # +clear+:: +Bool+ whether to clear the selection prompt once this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - # +response+:: +Bool+ whether show the rendered response when this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - def run clear, response - # render the - IOHelper.render( update_prompt ) - # loop through user confirm - # IOHelper.read_char - IOHelper.read_key_while true do |key| - raw = IOHelper.char_to_raw(key) - - case raw - when "y","Y" - @value = true - false - when "n","N" - @value = false - false - when "return" - @value = @default - false - else - true + def update_prompt + # call the renderer + @prompt = @renderer.render(@question, @default) end - end + def update_response + @prompt = @responseRenderer.renderResponse(@question, (@value)? 'Yes' : 'No') + end - # clear the final prompt and the line - IOHelper.clear if clear + # Run the list selection, wait for the user to select an item and return + # the selected index + # Params: + # +clear+:: +Bool+ whether to clear the selection prompt once this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + # +response+:: +Bool+ whether show the rendered response when this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + def run clear, response + # render the + IOHelper.render( update_prompt ) + # loop through user confirm + # IOHelper.read_char + IOHelper.read_key_while true do |key| + raw = IOHelper.char_to_raw(key) + + case raw + when "y","Y" + @value = true + false + when "n","N" + @value = false + false + when "return" + @value = @default + false + else + true + end + + end + + # clear the final prompt and the line + IOHelper.clear if clear + + # show the answer + IOHelper.render( update_response ) if response + + # return the value + @value + end - # show the answer - IOHelper.render( update_response ) if response + def self.ask question = nil, **opts + l = Confirm.new question, opts.fetch(:default, true), **opts + l.run opts.fetch(:clear, true), opts.fetch(:response, true) + end - # return the value - @value - end + end - def self.ask question = nil, **opts - l = Confirm.new question, opts.fetch(:default, true), **opts - l.run opts.fetch(:clear, true), opts.fetch(:response, true) end - end diff --git a/lib/inquirer/prompts/input.rb b/lib/inquirer/prompts/input.rb index a8a2ed2..3c53703 100644 --- a/lib/inquirer/prompts/input.rb +++ b/lib/inquirer/prompts/input.rb @@ -1,141 +1,148 @@ require 'term/ansicolor' -# Base rendering for input -module InputRenderer - def render heading = nil, value = nil, default = nil, footer = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the defaults - ( default.nil? ? "" : @default % default ) + - # render the list - ( value.nil? ? "" : @value % value ) + - # render the footer - ( footer.nil? ? "" : @footer % footer ) - end +module Ask + module Prompts + PROMPTS << :input - def renderResponse heading = nil, response = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the footer - ( response.nil? ? "" : @response % response ) - end -end + # Base rendering for input + module InputRenderer + def render heading = nil, value = nil, default = nil, footer = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the defaults + ( default.nil? ? "" : @default % default ) + + # render the list + ( value.nil? ? "" : @value % value ) + + # render the footer + ( footer.nil? ? "" : @footer % footer ) + end -# Default formatting for list rendering -class InputDefault - include InputRenderer - C = Term::ANSIColor - def initialize( style ) - @heading = "%s: " - @default = "(%s) " - @value = "%s" - @footer = "%s" - end -end + def renderResponse heading = nil, response = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the footer + ( response.nil? ? "" : @response % response ) + end + end -# Default formatting for response -class InputResponseDefault - include InputRenderer - C = Term::ANSIColor - def initialize( style = nil ) - @heading = "%s: " - @response = C.cyan("%s") + "\n" - end -end + # Default formatting for list rendering + class InputDefault + include InputRenderer + C = Term::ANSIColor + def initialize( style ) + @heading = "%s: " + @default = "(%s) " + @value = "%s" + @footer = "%s" + end + end -class Input - def initialize(question = nil, - default: nil, - renderer: nil, - responseRenderer: nil, - password: false, - **kwargs) - @question = question - @value = "" - @default = default - @prompt = "" - @password = password - @pos = 0 - @renderer = renderer || InputDefault.new( Inquirer::Style::Default ) - @responseRenderer = responseRenderer = InputResponseDefault.new() - end + # Default formatting for response + class InputResponseDefault + include InputRenderer + C = Term::ANSIColor + def initialize( style = nil ) + @heading = "%s: " + @response = C.cyan("%s") + "\n" + end + end - def display_value - return @value unless @password - @value.tr("^\n", '*') - end + class Input + def initialize(question = nil, + default: nil, + renderer: nil, + responseRenderer: nil, + password: false, + **kwargs) + @question = question + @value = "" + @default = default + @prompt = "" + @password = password + @pos = 0 + @renderer = renderer || InputDefault.new( Inquirer::Style::Default ) + @responseRenderer = responseRenderer = InputResponseDefault.new() + end - def update_prompt - # call the renderer - @prompt = @renderer.render(@question, display_value, @default) - end + def display_value + return @value unless @password + @value.tr("^\n", '*') + end - def update_response - @prompt = @responseRenderer.renderResponse(@question, display_value) - end + def update_prompt + # call the renderer + @prompt = @renderer.render(@question, display_value, @default) + end - def update_cursor - print IOHelper.char_left * @pos - end + def update_response + @prompt = @responseRenderer.renderResponse(@question, display_value) + end - # Run the list selection, wait for the user to select an item and return - # the selected index - # Params: - # +clear+:: +Bool+ whether to clear the selection prompt once this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - # +response+:: +Bool+ whether show the rendered response when this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - def run clear, response - # render the - IOHelper.render( update_prompt ) - # loop through user input - # IOHelper.read_char - IOHelper.read_key_while true do |key| - raw = IOHelper.char_to_raw(key) - - case raw - when "backspace" - @value = @value.chop - IOHelper.rerender( update_prompt ) - update_cursor - when "left" - if @pos < @value.length - @pos = @pos + 1 - print IOHelper.char_left - end - when "right" - if @pos > 0 - @pos = @pos - 1 - print IOHelper.char_right - end - when "return" - if not @default.nil? and @value == "" - @value = @default - end - else - unless ["up", "down"].include?(raw) - @value = @value.insert(@value.length - @pos, key) - IOHelper.rerender( update_prompt ) - update_cursor + def update_cursor + print IOHelper.char_left * @pos + end + + # Run the list selection, wait for the user to select an item and return + # the selected index + # Params: + # +clear+:: +Bool+ whether to clear the selection prompt once this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + # +response+:: +Bool+ whether show the rendered response when this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + def run clear, response + # render the + IOHelper.render( update_prompt ) + # loop through user input + # IOHelper.read_char + IOHelper.read_key_while true do |key| + raw = IOHelper.char_to_raw(key) + + case raw + when "backspace" + @value = @value.chop + IOHelper.rerender( update_prompt ) + update_cursor + when "left" + if @pos < @value.length + @pos = @pos + 1 + print IOHelper.char_left + end + when "right" + if @pos > 0 + @pos = @pos - 1 + print IOHelper.char_right + end + when "return" + if not @default.nil? and @value == "" + @value = @default + end + else + unless ["up", "down"].include?(raw) + @value = @value.insert(@value.length - @pos, key) + IOHelper.rerender( update_prompt ) + update_cursor + end + end + raw != "return" end + # clear the final prompt and the line + IOHelper.clear if clear + + # show the answer + IOHelper.render( update_response ) if response + + # return the value + @value end - raw != "return" - end - # clear the final prompt and the line - IOHelper.clear if clear - # show the answer - IOHelper.render( update_response ) if response + def self.ask question = nil, **opts + l = Input.new question, **opts + l.run opts.fetch(:clear, true), opts.fetch(:response, true) + end - # return the value - @value - end + end - def self.ask question = nil, **opts - l = Input.new question, **opts - l.run opts.fetch(:clear, true), opts.fetch(:response, true) end - end diff --git a/lib/inquirer/prompts/list.rb b/lib/inquirer/prompts/list.rb index f0109da..d1b2fdd 100644 --- a/lib/inquirer/prompts/list.rb +++ b/lib/inquirer/prompts/list.rb @@ -1,127 +1,134 @@ require 'term/ansicolor' -# Base rendering for simple lists -module ListRenderer - def render heading = nil, list = [], footer = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the list - list.map do |li| - render_item li - end.join("") + - # render the footer - ( footer.nil? ? "" : @footer % footer ) - end - - def renderResponse heading = nil, response = nil - # render the heading - ( heading.nil? ? "" : @heading % heading ) + - # render the footer - ( response.nil? ? "" : @response % response ) - end +module Ask + module Prompts + PROMPTS << :list + + # Base rendering for simple lists + module ListRenderer + def render heading = nil, list = [], footer = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the list + list.map do |li| + render_item li + end.join("") + + # render the footer + ( footer.nil? ? "" : @footer % footer ) + end - private + def renderResponse heading = nil, response = nil + # render the heading + ( heading.nil? ? "" : @heading % heading ) + + # render the footer + ( response.nil? ? "" : @response % response ) + end - def render_item x - ( x["selected"] ? @selector : " " ) + " " + - ( x["selected"] ? @selected_item : @item ) % x["value"] - end -end + private -# Default formatting for list rendering -class ListDefault - include ListRenderer - C = Term::ANSIColor - def initialize( style ) - @heading = "%s:\n" - @footer = "%s\n" - @item = "%s\n" - @selected_item = C.cyan("%s") + "\n" - @selector = C.cyan style.selector - end -end + def render_item x + ( x["selected"] ? @selector : " " ) + " " + + ( x["selected"] ? @selected_item : @item ) % x["value"] + end + end -# Default formatting for response -class ListResponseDefault - include ListRenderer - C = Term::ANSIColor - def initialize( style = nil ) - @heading = "%s: " - @response = C.cyan("%s") + "\n" - end -end + # Default formatting for list rendering + class ListDefault + include ListRenderer + C = Term::ANSIColor + def initialize( style ) + @heading = "%s:\n" + @footer = "%s\n" + @item = "%s\n" + @selected_item = C.cyan("%s") + "\n" + @selector = C.cyan style.selector + end + end -class List - def initialize(question = nil, - elements = [], - renderer: nil, - responseRenderer: nil, - **kwargs) - @elements = elements - @question = question - @pos = 0 - @prompt = "" - @renderer = renderer = ListDefault.new( Inquirer::Style::Default ) - @responseRenderer = responseRenderer = ListResponseDefault.new() - end + # Default formatting for response + class ListResponseDefault + include ListRenderer + C = Term::ANSIColor + def initialize( style = nil ) + @heading = "%s: " + @response = C.cyan("%s") + "\n" + end + end - def update_prompt - # transform the list into - # {"value"=>..., "selected"=> true|false} - e = @elements. - # attach the array position - map.with_index(0). - map do |c,pos| - { "value"=>c, "selected" => pos == @pos } + class List + def initialize(question = nil, + elements = [], + renderer: nil, + responseRenderer: nil, + **kwargs) + @elements = elements + @question = question + @pos = 0 + @prompt = "" + @renderer = renderer = ListDefault.new( Inquirer::Style::Default ) + @responseRenderer = responseRenderer = ListResponseDefault.new() end - # call the renderer - @prompt = @renderer.render(@question, e) - end - def update_response - @prompt = @responseRenderer.renderResponse(@question, @elements[@pos]) - end + def update_prompt + # transform the list into + # {"value"=>..., "selected"=> true|false} + e = @elements. + # attach the array position + map.with_index(0). + map do |c,pos| + { "value"=>c, "selected" => pos == @pos } + end + # call the renderer + @prompt = @renderer.render(@question, e) + end - # Run the list selection, wait for the user to select an item and return - # the selected index - # Params: - # +clear+:: +Bool+ whether to clear the selection prompt once this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - # +response+:: +Bool+ whether show the rendered response when this is done - # defaults to true; set it to false if you want the prompt to remain after - # the user is done with selecting - def run clear, response - # finish if there's nothing to do - return nil if Array(@elements).empty? - - # hides the cursor while prompting - IOHelper.without_cursor do - # render the - IOHelper.render( update_prompt ) - # loop through user input - IOHelper.read_key_while do |key| - @pos = (@pos - 1) % @elements.length if key == "up" - @pos = (@pos + 1) % @elements.length if key == "down" - IOHelper.rerender( update_prompt ) - # we are done if the user hits return - key != "return" + def update_response + @prompt = @responseRenderer.renderResponse(@question, @elements[@pos]) end - end - # clear the final prompt and the line - IOHelper.clear if clear + # Run the list selection, wait for the user to select an item and return + # the selected index + # Params: + # +clear+:: +Bool+ whether to clear the selection prompt once this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + # +response+:: +Bool+ whether show the rendered response when this is done + # defaults to true; set it to false if you want the prompt to remain after + # the user is done with selecting + def run clear, response + # finish if there's nothing to do + return nil if Array(@elements).empty? + + # hides the cursor while prompting + IOHelper.without_cursor do + # render the + IOHelper.render( update_prompt ) + # loop through user input + IOHelper.read_key_while do |key| + @pos = (@pos - 1) % @elements.length if key == "up" + @pos = (@pos + 1) % @elements.length if key == "down" + IOHelper.rerender( update_prompt ) + # we are done if the user hits return + key != "return" + end + end + + # clear the final prompt and the line + IOHelper.clear if clear + + # show the answer + IOHelper.render( update_response ) if response + + # return the index of the selected item + @pos + end - # show the answer - IOHelper.render( update_response ) if response + def self.ask question = nil, elements = [], **opts + l = List.new question, elements, **opts + l.run opts.fetch(:clear, true), opts.fetch(:response, true) + end - # return the index of the selected item - @pos - end + end - def self.ask question = nil, elements = [], **opts - l = List.new question, elements, **opts - l.run opts.fetch(:clear, true), opts.fetch(:response, true) end - end diff --git a/lib/inquirer/utils/iohelper.rb b/lib/inquirer/utils/iohelper.rb index f72bd9a..85eb386 100644 --- a/lib/inquirer/utils/iohelper.rb +++ b/lib/inquirer/utils/iohelper.rb @@ -1,129 +1,131 @@ require 'io/console' -module IOHelper - extend self +module Ask + module IOHelper + extend self - @rendered = "" + @rendered = "" - KEYS = { - " " => "space", - "\t" => "tab", - "\r" => "return", - "\n" => "linefeed", - "\e" => "escape", - "\e[A" => "up", - "\e[B" => "down", - "\e[C" => "right", - "\e[D" => "left", - "\177" => "backspace", - # ctrl + c - "\003" => "ctrl-c", - # ctrl + d - "\004" => "ctrl-d" - } + KEYS = { + " " => "space", + "\t" => "tab", + "\r" => "return", + "\n" => "linefeed", + "\e" => "escape", + "\e[A" => "up", + "\e[B" => "down", + "\e[C" => "right", + "\e[D" => "left", + "\177" => "backspace", + # ctrl + c + "\003" => "ctrl-c", + # ctrl + d + "\004" => "ctrl-d" + } - # Read a character the user enters on console. This call is synchronous blocking. - # This is taken from: http://www.alecjacobson.com/weblog/?p=75 - def read_char - begin - # save previous state of stty - old_state = `stty -g` - # disable echoing and enable raw (not having to press enter) - system "stty raw -echo" - c = STDIN.getc.chr - # gather next two characters of special keys - if(c=="\e") - extra_thread = Thread.new{ - c = c + STDIN.getc.chr - c = c + STDIN.getc.chr - } - # wait just long enough for special keys to get swallowed - extra_thread.join(0.00001) - # kill thread so not-so-long special keys don't wait on getc - extra_thread.kill + # Read a character the user enters on console. This call is synchronous blocking. + # This is taken from: http://www.alecjacobson.com/weblog/?p=75 + def read_char + begin + # save previous state of stty + old_state = `stty -g` + # disable echoing and enable raw (not having to press enter) + system "stty raw -echo" + c = STDIN.getc.chr + # gather next two characters of special keys + if(c=="\e") + extra_thread = Thread.new{ + c = c + STDIN.getc.chr + c = c + STDIN.getc.chr + } + # wait just long enough for special keys to get swallowed + extra_thread.join(0.00001) + # kill thread so not-so-long special keys don't wait on getc + extra_thread.kill + end + rescue => ex + puts "#{ex.class}: #{ex.message}" + puts ex.backtrace + ensure + # restore previous state of stty + system "stty #{old_state}" end - rescue => ex - puts "#{ex.class}: #{ex.message}" - puts ex.backtrace - ensure - # restore previous state of stty - system "stty #{old_state}" + return c end - return c - end - # Read a keypress on console. Return the key name (e.g. "space", "a", "B") - # Params: - # +with_exit_codes+:: +Bool+ whether to throw Interrupts when the user presses - # ctrl-c and ctrl-d. (true by default) - def read_key with_exit_codes = true, return_char = false - char = read_char - raise Interrupt if with_exit_codes and ( char == "\003" or char == "\004" ) - if return_char then char else char_to_raw char end - end + # Read a keypress on console. Return the key name (e.g. "space", "a", "B") + # Params: + # +with_exit_codes+:: +Bool+ whether to throw Interrupts when the user presses + # ctrl-c and ctrl-d. (true by default) + def read_key with_exit_codes = true, return_char = false + char = read_char + raise Interrupt if with_exit_codes and ( char == "\003" or char == "\004" ) + if return_char then char else char_to_raw char end + end - # Get each key the user presses and hand it one by one to the block. Do this - # as long as the block returns truthy - # Params: - # +&block+:: +Proc+ a block that receives a user key and returns truthy or falsy - def read_key_while return_char = false, &block - STDIN.noecho do - # as long as the block doen't return falsy, - # read the user input key and sned it to the block - while block.( IOHelper.read_key true, return_char ) + # Get each key the user presses and hand it one by one to the block. Do this + # as long as the block returns truthy + # Params: + # +&block+:: +Proc+ a block that receives a user key and returns truthy or falsy + def read_key_while return_char = false, &block + STDIN.noecho do + # as long as the block doen't return falsy, + # read the user input key and sned it to the block + while block.( IOHelper.read_key true, return_char ) + end end end - end - # Get the console window size - # Returns: [width, height] - def winsize - STDIN.winsize - end + # Get the console window size + # Returns: [width, height] + def winsize + STDIN.winsize + end - # Render a text to the prompt - def render prompt - @rendered = prompt - print prompt - end + # Render a text to the prompt + def render prompt + @rendered = prompt + print prompt + end - # Clear the prompt and render the update - def rerender prompt - clear - render prompt - end + # Clear the prompt and render the update + def rerender prompt + clear + render prompt + end - # clear the console based on the last text rendered - def clear - # get console window height and width - h,w = IOHelper.winsize - # determine how many lines to move up - n = @rendered.scan(/\n/).length - # jump back to the first position and clear the line - print carriage_return + ( line_up + clear_line ) * n + clear_line - end + # clear the console based on the last text rendered + def clear + # get console window height and width + h,w = IOHelper.winsize + # determine how many lines to move up + n = @rendered.scan(/\n/).length + # jump back to the first position and clear the line + print carriage_return + ( line_up + clear_line ) * n + clear_line + end - # hides the cursor and ensure the curso be visible at the end - def without_cursor - # tell the terminal to hide the cursor - print `tput civis` - begin - # run the block - yield - ensure - # tell the terminal to show the cursor - print `tput cnorm` + # hides the cursor and ensure the curso be visible at the end + def without_cursor + # tell the terminal to hide the cursor + print `tput civis` + begin + # run the block + yield + ensure + # tell the terminal to show the cursor + print `tput cnorm` + end end - end - def char_to_raw char - KEYS.fetch char, char - end + def char_to_raw char + KEYS.fetch char, char + end - def carriage_return; "\r" end - def line_up; "\e[A" end - def clear_line; "\e[0K" end - def char_left; "\e[D" end - def char_right; "\e[C" end + def carriage_return; "\r" end + def line_up; "\e[A" end + def clear_line; "\e[0K" end + def char_left; "\e[D" end + def char_right; "\e[C" end + end end diff --git a/test/classes/checkbox_spec.rb b/test/classes/checkbox_spec.rb index 80335a5..544af83 100644 --- a/test/classes/checkbox_spec.rb +++ b/test/classes/checkbox_spec.rb @@ -1,14 +1,14 @@ # encoding: utf-8 require 'minitest_helper' -describe Checkbox do +describe Ask::Prompts::Checkbox do before :each do IOHelper.output = "" IOHelper.keys = nil end [ - Checkbox.method(:ask), + Ask::Prompts::Checkbox.method(:ask), Ask.method(:checkbox) ].each do |meth| diff --git a/test/classes/confirm_spec.rb b/test/classes/confirm_spec.rb index 4bfbc7f..25c1f7b 100644 --- a/test/classes/confirm_spec.rb +++ b/test/classes/confirm_spec.rb @@ -1,13 +1,13 @@ # encoding: utf-8 require 'minitest_helper' -describe Confirm do +describe Ask::Prompts::Confirm do before :each do IOHelper.output = "" end [ - Confirm.method(:ask), + Ask::Prompts::Confirm.method(:ask), Ask.method(:confirm) ].each do |meth| diff --git a/test/classes/input_spec.rb b/test/classes/input_spec.rb index 02e71ae..3ab503d 100644 --- a/test/classes/input_spec.rb +++ b/test/classes/input_spec.rb @@ -1,14 +1,14 @@ # encoding: utf-8 require 'minitest_helper' -describe Input do +describe Ask::Prompts::Input do before :each do IOHelper.output = "" IOHelper.keys = ['t','y','p','e','d',' ','i','n','p','u','t',"\r"] end [ - Input.method(:ask), + Ask::Prompts::Input.method(:ask), Ask.method(:input) ].each do |meth| diff --git a/test/classes/list_spec.rb b/test/classes/list_spec.rb index 87fddff..f3e0eeb 100644 --- a/test/classes/list_spec.rb +++ b/test/classes/list_spec.rb @@ -1,14 +1,14 @@ # encoding: utf-8 require 'minitest_helper' -describe List do +describe Ask::Prompts::List do before :each do IOHelper.output = "" IOHelper.keys = nil end [ - List.method(:ask), + Ask::Prompts::List.method(:ask), Ask.method(:list) ].each do |meth| diff --git a/test/minitest_helper.rb b/test/minitest_helper.rb index 76e7040..d13d5fc 100644 --- a/test/minitest_helper.rb +++ b/test/minitest_helper.rb @@ -22,7 +22,7 @@ # overload all necessary methods of iohelper # this will serve as a mock helper to read input and output -module IOHelper +module Ask::IOHelper extend self attr_accessor :output, :keys def render sth @@ -40,3 +40,4 @@ def read_key_while return_char = false, &block end end end +IOHelper = Ask::IOHelper