rfix 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/exe/rfix +11 -90
  3. data/lib/rfix.rb +10 -9
  4. data/lib/rfix/branch/reference.rb +2 -2
  5. data/lib/rfix/branch/upstream.rb +2 -4
  6. data/lib/rfix/cli/command.rb +14 -1
  7. data/lib/rfix/cli/command/all.rb +21 -0
  8. data/lib/rfix/cli/command/base.rb +30 -19
  9. data/lib/rfix/cli/command/branch.rb +2 -0
  10. data/lib/rfix/cli/command/help.rb +2 -0
  11. data/lib/rfix/cli/command/info.rb +6 -1
  12. data/lib/rfix/cli/command/local.rb +2 -0
  13. data/lib/rfix/cli/command/origin.rb +2 -0
  14. data/lib/rfix/cli/command/setup.rb +2 -0
  15. data/lib/rfix/cli/command/status.rb +39 -0
  16. data/lib/rfix/collector.rb +69 -0
  17. data/lib/rfix/diff.rb +69 -0
  18. data/lib/rfix/extension/comment_config.rb +15 -0
  19. data/lib/rfix/extension/offense.rb +17 -14
  20. data/lib/rfix/extension/pastel.rb +7 -4
  21. data/lib/rfix/extension/progresbar.rb +15 -0
  22. data/lib/rfix/extension/strings.rb +10 -2
  23. data/lib/rfix/file.rb +5 -3
  24. data/lib/rfix/file/base.rb +21 -14
  25. data/lib/rfix/file/deleted.rb +2 -0
  26. data/lib/rfix/file/ignored.rb +2 -0
  27. data/lib/rfix/file/null.rb +17 -0
  28. data/lib/rfix/file/tracked.rb +39 -23
  29. data/lib/rfix/file/undefined.rb +17 -0
  30. data/lib/rfix/file/untracked.rb +3 -1
  31. data/lib/rfix/formatter.rb +67 -71
  32. data/lib/rfix/highlighter.rb +1 -3
  33. data/lib/rfix/rake/gemfile.rb +26 -23
  34. data/lib/rfix/repository.rb +59 -96
  35. data/lib/rfix/types.rb +24 -14
  36. data/lib/rfix/version.rb +1 -1
  37. data/rfix.gemspec +11 -3
  38. data/vendor/cli-ui/Gemfile +17 -0
  39. data/vendor/cli-ui/Gemfile.lock +60 -0
  40. data/vendor/cli-ui/LICENSE.txt +21 -0
  41. data/vendor/cli-ui/README.md +224 -0
  42. data/vendor/cli-ui/Rakefile +20 -0
  43. data/vendor/cli-ui/bin/console +14 -0
  44. data/vendor/cli-ui/cli-ui.gemspec +25 -0
  45. data/vendor/cli-ui/dev.yml +14 -0
  46. data/vendor/cli-ui/lib/cli/ui.rb +233 -0
  47. data/vendor/cli-ui/lib/cli/ui/ansi.rb +157 -0
  48. data/vendor/cli-ui/lib/cli/ui/color.rb +84 -0
  49. data/vendor/cli-ui/lib/cli/ui/formatter.rb +192 -0
  50. data/vendor/cli-ui/lib/cli/ui/frame.rb +269 -0
  51. data/vendor/cli-ui/lib/cli/ui/frame/frame_stack.rb +98 -0
  52. data/vendor/cli-ui/lib/cli/ui/frame/frame_style.rb +120 -0
  53. data/vendor/cli-ui/lib/cli/ui/frame/frame_style/box.rb +166 -0
  54. data/vendor/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +139 -0
  55. data/vendor/cli-ui/lib/cli/ui/glyph.rb +84 -0
  56. data/vendor/cli-ui/lib/cli/ui/os.rb +67 -0
  57. data/vendor/cli-ui/lib/cli/ui/printer.rb +59 -0
  58. data/vendor/cli-ui/lib/cli/ui/progress.rb +90 -0
  59. data/vendor/cli-ui/lib/cli/ui/prompt.rb +297 -0
  60. data/vendor/cli-ui/lib/cli/ui/prompt/interactive_options.rb +484 -0
  61. data/vendor/cli-ui/lib/cli/ui/prompt/options_handler.rb +29 -0
  62. data/vendor/cli-ui/lib/cli/ui/spinner.rb +66 -0
  63. data/vendor/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
  64. data/vendor/cli-ui/lib/cli/ui/spinner/spin_group.rb +263 -0
  65. data/vendor/cli-ui/lib/cli/ui/stdout_router.rb +232 -0
  66. data/vendor/cli-ui/lib/cli/ui/terminal.rb +46 -0
  67. data/vendor/cli-ui/lib/cli/ui/truncater.rb +102 -0
  68. data/vendor/cli-ui/lib/cli/ui/version.rb +5 -0
  69. data/vendor/cli-ui/lib/cli/ui/widgets.rb +77 -0
  70. data/vendor/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  71. data/vendor/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  72. data/vendor/cli-ui/lib/cli/ui/wrap.rb +56 -0
  73. data/vendor/cli-ui/test/cli/ui/ansi_test.rb +32 -0
  74. data/vendor/cli-ui/test/cli/ui/cli_ui_test.rb +23 -0
  75. data/vendor/cli-ui/test/cli/ui/color_test.rb +40 -0
  76. data/vendor/cli-ui/test/cli/ui/formatter_test.rb +79 -0
  77. data/vendor/cli-ui/test/cli/ui/glyph_test.rb +68 -0
  78. data/vendor/cli-ui/test/cli/ui/printer_test.rb +103 -0
  79. data/vendor/cli-ui/test/cli/ui/progress_test.rb +46 -0
  80. data/vendor/cli-ui/test/cli/ui/prompt/options_handler_test.rb +39 -0
  81. data/vendor/cli-ui/test/cli/ui/prompt_test.rb +348 -0
  82. data/vendor/cli-ui/test/cli/ui/spinner/spin_group_test.rb +39 -0
  83. data/vendor/cli-ui/test/cli/ui/spinner_test.rb +141 -0
  84. data/vendor/cli-ui/test/cli/ui/stdout_router_test.rb +32 -0
  85. data/vendor/cli-ui/test/cli/ui/terminal_test.rb +26 -0
  86. data/vendor/cli-ui/test/cli/ui/truncater_test.rb +31 -0
  87. data/vendor/cli-ui/test/cli/ui/widgets/status_test.rb +49 -0
  88. data/vendor/cli-ui/test/cli/ui/widgets_test.rb +15 -0
  89. data/vendor/cli-ui/test/test_helper.rb +53 -0
  90. data/vendor/cli-ui/tmp/cache/bootsnap/compile-cache/d9/c036af0f3dc494 +0 -0
  91. data/vendor/cli-ui/tmp/cache/bootsnap/load-path-cache +0 -0
  92. data/vendor/dry-cli/lib/dry/cli/command.rb +2 -1
  93. data/vendor/dry-cli/tmp/cache/bootsnap/compile-cache/ff/a22a5daafbd74c +0 -0
  94. data/vendor/dry-cli/tmp/cache/bootsnap/load-path-cache +0 -0
  95. data/vendor/strings-ansi/tmp/cache/bootsnap/compile-cache/79/49cf49407b370e +0 -0
  96. data/vendor/strings-ansi/tmp/cache/bootsnap/load-path-cache +0 -0
  97. metadata +170 -9
  98. data/lib/rfix/extension/string.rb +0 -12
  99. data/lib/rfix/indicator.rb +0 -19
@@ -0,0 +1,84 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ class Glyph
6
+ class InvalidGlyphHandle < ArgumentError
7
+ def initialize(handle)
8
+ super
9
+ @handle = handle
10
+ end
11
+
12
+ def message
13
+ keys = Glyph.available.join(',')
14
+ "invalid glyph handle: #{@handle} " \
15
+ "-- must be one of CLI::UI::Glyph.available (#{keys})"
16
+ end
17
+ end
18
+
19
+ attr_reader :handle, :codepoint, :color, :to_s, :fmt
20
+
21
+ # Creates a new glyph
22
+ #
23
+ # ==== Attributes
24
+ #
25
+ # * +handle+ - The handle in the +MAP+ constant
26
+ # * +codepoint+ - The codepoint used to create the glyph (e.g. +0x2717+ for a ballot X)
27
+ # * +plain+ - A fallback plain string to be used in case glyphs are disabled
28
+ # * +color+ - What color to output the glyph. Check +CLI::UI::Color+ for options.
29
+ #
30
+ def initialize(handle, codepoint, plain, color)
31
+ @handle = handle
32
+ @codepoint = codepoint
33
+ @color = color
34
+ @plain = plain
35
+ @char = Array(codepoint).pack('U*')
36
+ @to_s = color.code + char + Color::RESET.code
37
+ @fmt = "{{#{color.name}:#{char}}}"
38
+
39
+ MAP[handle] = self
40
+ end
41
+
42
+ # Fetches the actual character(s) to be displayed for a glyph, based on the current OS support
43
+ #
44
+ # ==== Returns
45
+ # Returns the glyph string
46
+ def char
47
+ CLI::UI::OS.current.supports_emoji? ? @char : @plain
48
+ end
49
+
50
+ # Mapping of glyphs to terminal output
51
+ MAP = {}
52
+ STAR = new('*', 0x2b51, '*', Color::YELLOW) # YELLOW SMALL STAR (⭑)
53
+ INFO = new('i', 0x1d4be, 'i', Color::BLUE) # BLUE MATHEMATICAL SCRIPT SMALL i (𝒾)
54
+ QUESTION = new('?', 0x003f, '?', Color::BLUE) # BLUE QUESTION MARK (?)
55
+ CHECK = new('v', 0x2713, '√', Color::GREEN) # GREEN CHECK MARK (✓)
56
+ X = new('x', 0x2717, 'X', Color::RED) # RED BALLOT X (✗)
57
+ BUG = new('b', 0x1f41b, '!', Color::WHITE) # Bug emoji (🐛)
58
+ CHEVRON = new('>', 0xbb, '»', Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (»)
59
+ HOURGLASS = new('H', [0x231b, 0xfe0e], 'H', Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (⌛︎)
60
+ WARNING = new('!', [0x26a0, 0xfe0f], '!', Color::YELLOW) # WARNING SIGN + VARIATION SELECTOR 16 (⚠️ )
61
+
62
+ # Looks up a glyph by name
63
+ #
64
+ # ==== Raises
65
+ # Raises a InvalidGlyphHandle if the glyph is not available
66
+ # You likely need to create it with +.new+ or you made a typo
67
+ #
68
+ # ==== Returns
69
+ # Returns a terminal output-capable string
70
+ #
71
+ def self.lookup(name)
72
+ MAP.fetch(name.to_s)
73
+ rescue KeyError
74
+ raise InvalidGlyphHandle, name
75
+ end
76
+
77
+ # All available glyphs by name
78
+ #
79
+ def self.available
80
+ MAP.keys
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,67 @@
1
+ require 'rbconfig'
2
+
3
+ module CLI
4
+ module UI
5
+ module OS
6
+ # Determines which OS is currently running the UI, to make it easier to
7
+ # adapt its behaviour to the features of the OS.
8
+ def self.current
9
+ @current_os ||= case RbConfig::CONFIG['host_os']
10
+ when /darwin/
11
+ Mac
12
+ when /linux/
13
+ Linux
14
+ else
15
+ if RUBY_PLATFORM !~ /cygwin/ && ENV['OS'] == 'Windows_NT'
16
+ Windows
17
+ else
18
+ raise "Could not determine OS from host_os #{RbConfig::CONFIG["host_os"]}"
19
+ end
20
+ end
21
+ end
22
+
23
+ class Mac
24
+ class << self
25
+ def supports_emoji?
26
+ true
27
+ end
28
+
29
+ def supports_color_prompt?
30
+ true
31
+ end
32
+
33
+ def supports_arrow_keys?
34
+ true
35
+ end
36
+
37
+ def shift_cursor_on_line_reset?
38
+ false
39
+ end
40
+ end
41
+ end
42
+
43
+ class Linux < Mac
44
+ end
45
+
46
+ class Windows
47
+ class << self
48
+ def supports_emoji?
49
+ false
50
+ end
51
+
52
+ def supports_color_prompt?
53
+ false
54
+ end
55
+
56
+ def supports_arrow_keys?
57
+ false
58
+ end
59
+
60
+ def shift_cursor_on_line_reset?
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ class Printer
6
+ # Print a message to a stream with common utilities.
7
+ # Allows overriding the color, encoding, and target stream.
8
+ # By default, it formats the string using CLI:UI and rescues common stream errors.
9
+ #
10
+ # ==== Attributes
11
+ #
12
+ # * +msg+ - (required) the string to output. Can be frozen.
13
+ #
14
+ # ==== Options
15
+ #
16
+ # * +:frame_color+ - Override the frame color. Defaults to nil.
17
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
18
+ # * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
19
+ # * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
20
+ # * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
21
+ # * +:wrap+ - Whether to wrap text at word boundaries to terminal width. Defaults to true.
22
+ #
23
+ # ==== Returns
24
+ # Returns whether the message was successfully printed,
25
+ # which can be useful if +:graceful+ is set to true.
26
+ #
27
+ # ==== Example
28
+ #
29
+ # CLI::UI::Printer.puts('{{x}} Ouch', to: $stderr)
30
+ #
31
+ def self.puts(
32
+ msg,
33
+ frame_color:
34
+ nil,
35
+ to:
36
+ $stdout,
37
+ encoding: Encoding::UTF_8,
38
+ format: true,
39
+ graceful: true,
40
+ wrap: true
41
+ )
42
+ msg = (+msg).force_encoding(encoding) if encoding
43
+ msg = CLI::UI.fmt(msg) if format
44
+ msg = CLI::UI.wrap(msg) if wrap
45
+
46
+ if frame_color
47
+ CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
48
+ else
49
+ to.puts(msg)
50
+ end
51
+
52
+ true
53
+ rescue Errno::EIO, Errno::EPIPE, IOError => e
54
+ raise(e) unless graceful
55
+ false
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ class Progress
6
+ # A Cyan filled block
7
+ FILLED_BAR = "\e[46m"
8
+ # A bright white block
9
+ UNFILLED_BAR = "\e[1;47m"
10
+
11
+ # Add a progress bar to the terminal output
12
+ #
13
+ # https://user-images.githubusercontent.com/3074765/33799794-cc4c940e-dd00-11e7-9bdc-90f77ec9167c.gif
14
+ #
15
+ # ==== Example Usage:
16
+ #
17
+ # Set the percent to X
18
+ # CLI::UI::Progress.progress do |bar|
19
+ # bar.tick(set_percent: percent)
20
+ # end
21
+ #
22
+ # Increase the percent by 1 percent
23
+ # CLI::UI::Progress.progress do |bar|
24
+ # bar.tick
25
+ # end
26
+ #
27
+ # Increase the percent by X
28
+ # CLI::UI::Progress.progress do |bar|
29
+ # bar.tick(percent: 0.05)
30
+ # end
31
+ def self.progress(width: Terminal.width)
32
+ bar = Progress.new(width: width)
33
+ print(CLI::UI::ANSI.hide_cursor)
34
+ yield(bar)
35
+ ensure
36
+ puts bar.to_s
37
+ CLI::UI.raw do
38
+ print(ANSI.show_cursor)
39
+ end
40
+ end
41
+
42
+ # Initialize a progress bar. Typically used in a +Progress.progress+ block
43
+ #
44
+ # ==== Options
45
+ # One of the follow can be used, but not both together
46
+ #
47
+ # * +:width+ - The width of the terminal
48
+ #
49
+ def initialize(width: Terminal.width)
50
+ @percent_done = 0
51
+ @max_width = width
52
+ end
53
+
54
+ # Set the progress of the bar. Typically used in a +Progress.progress+ block
55
+ #
56
+ # ==== Options
57
+ # One of the follow can be used, but not both together
58
+ #
59
+ # * +:percent+ - Increment progress by a specific percent amount
60
+ # * +:set_percent+ - Set progress to a specific percent
61
+ #
62
+ # *Note:* The +:percent+ and +:set_percent must be between 0.00 and 1.0
63
+ #
64
+ def tick(percent: 0.01, set_percent: nil)
65
+ raise ArgumentError, 'percent and set_percent cannot both be specified' if percent != 0.01 && set_percent
66
+ @percent_done += percent
67
+ @percent_done = set_percent if set_percent
68
+ @percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
69
+
70
+ print(to_s)
71
+ print(CLI::UI::ANSI.previous_line + "\n")
72
+ end
73
+
74
+ # Format the progress bar to be printed to terminal
75
+ #
76
+ def to_s
77
+ suffix = " #{(@percent_done * 100).floor}%".ljust(5)
78
+ workable_width = @max_width - Frame.prefix_width - suffix.size
79
+ filled = [(@percent_done * workable_width.to_f).ceil, 0].max
80
+ unfilled = [workable_width - filled, 0].max
81
+
82
+ CLI::UI.resolve_text([
83
+ FILLED_BAR + ' ' * filled,
84
+ UNFILLED_BAR + ' ' * unfilled,
85
+ CLI::UI::Color::RESET.code + suffix,
86
+ ].join)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,297 @@
1
+ # coding: utf-8
2
+ require 'cli/ui'
3
+ require 'readline'
4
+
5
+ module Readline
6
+ unless const_defined?(:FILENAME_COMPLETION_PROC)
7
+ FILENAME_COMPLETION_PROC = proc do |input|
8
+ directory = input[-1] == '/' ? input : File.dirname(input)
9
+ filename = input[-1] == '/' ? '' : File.basename(input)
10
+
11
+ (Dir.entries(directory).select do |fp|
12
+ fp.start_with?(filename)
13
+ end - (input[-1] == '.' ? [] : ['.', '..'])).map do |fp|
14
+ File.join(directory, fp).gsub(/\A\.\//, '')
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module CLI
21
+ module UI
22
+ module Prompt
23
+ autoload :InteractiveOptions, 'cli/ui/prompt/interactive_options'
24
+ autoload :OptionsHandler, 'cli/ui/prompt/options_handler'
25
+ private_constant :InteractiveOptions, :OptionsHandler
26
+
27
+ class << self
28
+ # Ask a user a question with either free form answer or a set of answers (multiple choice)
29
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control multiple choice selection
30
+ # Do not use this method for yes/no questions. Use +confirm+
31
+ #
32
+ # * Handles free form answers (options are nil)
33
+ # * Handles default answers for free form text
34
+ # * Handles file auto completion for file input
35
+ # * Handles interactively choosing answers using +InteractiveOptions+
36
+ #
37
+ # https://user-images.githubusercontent.com/3074765/33799822-47f23302-dd01-11e7-82f3-9072a5a5f611.png
38
+ #
39
+ # ==== Attributes
40
+ #
41
+ # * +question+ - (required) The question to ask the user
42
+ #
43
+ # ==== Options
44
+ #
45
+ # * +:options+ - Options that the user may select from. Will use +InteractiveOptions+ to do so.
46
+ # * +:default+ - The default answer to the question (e.g. they just press enter and don't input anything)
47
+ # * +:is_file+ - Tells the input to use file auto-completion (tab completion)
48
+ # * +:allow_empty+ - Allows the answer to be empty
49
+ # * +:multiple+ - Allow multiple options to be selected
50
+ # * +:filter_ui+ - Enable option filtering (default: true)
51
+ # * +:select_ui+ - Enable long-form option selection (default: true)
52
+ #
53
+ # Note:
54
+ # * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+,
55
+ # you cannot set options with either of these keywords
56
+ # * +:default+ conflicts with +:allow_empty:, you cannot set these together
57
+ # * +:options+ conflicts with providing a +Block+ , you may only set one
58
+ # * +:multiple+ can only be used with +:options+ or a +Block+; it is ignored, otherwise.
59
+ #
60
+ # ==== Block (optional)
61
+ #
62
+ # * A Proc that provides a +OptionsHandler+ and uses the public +:option+ method to add options and their
63
+ # respective handlers
64
+ #
65
+ # ==== Return Value
66
+ #
67
+ # * If a +Block+ was not provided, the selected option or response to the free form question will be returned
68
+ # * If a +Block+ was provided, the evaluated value of the +Block+ will be returned
69
+ #
70
+ # ==== Example Usage:
71
+ #
72
+ # Free form question
73
+ # CLI::UI::Prompt.ask('What color is the sky?')
74
+ #
75
+ # Free form question with a file answer
76
+ # CLI::UI::Prompt.ask('Where is your Gemfile located?', is_file: true)
77
+ #
78
+ # Free form question with a default answer
79
+ # CLI::UI::Prompt.ask('What color is the sky?', default: 'blue')
80
+ #
81
+ # Free form question when the answer can be empty
82
+ # CLI::UI::Prompt.ask('What is your opinion on this question?', allow_empty: true)
83
+ #
84
+ # Interactive (multiple choice) question
85
+ # CLI::UI::Prompt.ask('What kind of project is this?', options: %w(rails go ruby python))
86
+ #
87
+ # Interactive (multiple choice) question with defined handlers
88
+ # CLI::UI::Prompt.ask('What kind of project is this?') do |handler|
89
+ # handler.option('rails') { |selection| selection }
90
+ # handler.option('go') { |selection| selection }
91
+ # handler.option('ruby') { |selection| selection }
92
+ # handler.option('python') { |selection| selection }
93
+ # end
94
+ #
95
+ def ask(
96
+ question,
97
+ options: nil,
98
+ default: nil,
99
+ is_file: nil,
100
+ allow_empty: true,
101
+ multiple: false,
102
+ filter_ui: true,
103
+ select_ui: true,
104
+ &options_proc
105
+ )
106
+ if (options || block_given?) && ((default && !multiple) || is_file)
107
+ raise(ArgumentError, 'conflicting arguments: options provided with default or is_file')
108
+ end
109
+
110
+ if options && multiple && default && !(default - options).empty?
111
+ raise(ArgumentError, 'conflicting arguments: default should only include elements present in options')
112
+ end
113
+
114
+ if options || block_given?
115
+ ask_interactive(
116
+ question,
117
+ options,
118
+ multiple: multiple,
119
+ default: default,
120
+ filter_ui: filter_ui,
121
+ select_ui: select_ui,
122
+ &options_proc
123
+ )
124
+ else
125
+ ask_free_form(question, default, is_file, allow_empty)
126
+ end
127
+ end
128
+
129
+ # Asks the user for a single-line answer, without displaying the characters while typing.
130
+ # Typically used for password prompts
131
+ #
132
+ # ==== Return Value
133
+ #
134
+ # The password, without a trailing newline.
135
+ # If the user simply presses "Enter" without typing any password, this will return an empty string.
136
+ def ask_password(question)
137
+ require 'io/console'
138
+
139
+ CLI::UI.with_frame_color(:blue) do
140
+ STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
141
+
142
+ # noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
143
+ # No fancy Readline integration (like echoing back) is required for a password prompt anyway.
144
+ password = STDIN.noecho do
145
+ # Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
146
+ # " 123 \n".chomp => " 123 "
147
+ STDIN.gets.chomp
148
+ end
149
+
150
+ STDOUT.puts # Complete the line
151
+
152
+ password
153
+ end
154
+ end
155
+
156
+ # Asks the user a yes/no question.
157
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control
158
+ #
159
+ # ==== Example Usage:
160
+ #
161
+ # Confirmation question
162
+ # CLI::UI::Prompt.confirm('Is the sky blue?')
163
+ #
164
+ # CLI::UI::Prompt.confirm('Do a dangerous thing?', default: false)
165
+ #
166
+ def confirm(question, default: true)
167
+ ask_interactive(question, default ? %w(yes no) : %w(no yes), filter_ui: false) == 'yes'
168
+ end
169
+
170
+ private
171
+
172
+ def ask_free_form(question, default, is_file, allow_empty)
173
+ if default && !allow_empty
174
+ raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false')
175
+ end
176
+
177
+ if default
178
+ puts_question("#{question} (empty = #{default})")
179
+ else
180
+ puts_question(question)
181
+ end
182
+
183
+ # Ask a free form question
184
+ loop do
185
+ line = readline(is_file: is_file)
186
+
187
+ if line.empty? && default
188
+ write_default_over_empty_input(default)
189
+ return default
190
+ end
191
+
192
+ if !line.empty? || allow_empty
193
+ return line
194
+ end
195
+ end
196
+ end
197
+
198
+ def ask_interactive(question, options = nil, multiple: false, default: nil, filter_ui: true, select_ui: true)
199
+ raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
200
+
201
+ options ||= if block_given?
202
+ handler = OptionsHandler.new
203
+ yield handler
204
+ handler.options
205
+ end
206
+
207
+ raise(ArgumentError, 'insufficient options') if options.nil? || options.empty?
208
+ navigate_text = if CLI::UI::OS.current.supports_arrow_keys?
209
+ 'Choose with ↑ ↓ ⏎'
210
+ else
211
+ "Navigate up with 'k' and down with 'j', press Enter to select"
212
+ end
213
+
214
+ instructions = (multiple ? 'Toggle options. ' : '') + navigate_text
215
+ instructions += ", filter with 'f'" if filter_ui
216
+ instructions += ", enter option with 'e'" if select_ui && (options.size > 9)
217
+ puts_question("#{question} {{yellow:(#{instructions})}}")
218
+ resp = interactive_prompt(options, multiple: multiple, default: default)
219
+
220
+ # Clear the line
221
+ print(ANSI.previous_line + ANSI.clear_to_end_of_line)
222
+ # Force StdoutRouter to prefix
223
+ print(ANSI.previous_line + "\n")
224
+
225
+ # reset the question to include the answer
226
+ resp_text = resp
227
+ if multiple
228
+ resp_text = case resp.size
229
+ when 0
230
+ '<nothing>'
231
+ when 1..2
232
+ resp.join(' and ')
233
+ else
234
+ "#{resp.size} items"
235
+ end
236
+ end
237
+ puts_question("#{question} (You chose: {{italic:#{resp_text}}})")
238
+
239
+ return handler.call(resp) if block_given?
240
+ resp
241
+ end
242
+
243
+ # Useful for stubbing in tests
244
+ def interactive_prompt(options, multiple: false, default: nil)
245
+ InteractiveOptions.call(options, multiple: multiple, default: default)
246
+ end
247
+
248
+ def write_default_over_empty_input(default)
249
+ CLI::UI.raw do
250
+ STDERR.puts(
251
+ CLI::UI::ANSI.cursor_up(1) +
252
+ "\r" +
253
+ CLI::UI::ANSI.cursor_forward(4) + # TODO: width
254
+ default +
255
+ CLI::UI::Color::RESET.code
256
+ )
257
+ end
258
+ end
259
+
260
+ def puts_question(str)
261
+ CLI::UI.with_frame_color(:blue) do
262
+ STDOUT.puts(CLI::UI.fmt('{{?}} ' + str))
263
+ end
264
+ end
265
+
266
+ def readline(is_file: false)
267
+ if is_file
268
+ Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
269
+ Readline.completion_append_character = ''
270
+ else
271
+ Readline.completion_proc = proc { |*| nil }
272
+ Readline.completion_append_character = ' '
273
+ end
274
+
275
+ # because Readline is a C library, CLI::UI's hooks into $stdout don't
276
+ # work. We could work around this by having CLI::UI use a pipe and a
277
+ # thread to manage output, but the current strategy feels like a
278
+ # better tradeoff.
279
+ prefix = CLI::UI.with_frame_color(:blue) { CLI::UI::Frame.prefix }
280
+ # If a prompt is interrupted on Windows it locks the colour of the terminal from that point on, so we should
281
+ # not change the colour here.
282
+ prompt = prefix + CLI::UI.fmt('{{blue:> }}')
283
+ prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.supports_color_prompt?
284
+
285
+ begin
286
+ line = Readline.readline(prompt, true)
287
+ print(CLI::UI::Color::RESET.code)
288
+ line.to_s.chomp
289
+ rescue Interrupt
290
+ CLI::UI.raw { STDERR.puts('^C' + CLI::UI::Color::RESET.code) }
291
+ raise
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end