Module: Hammer::Shell
- Included in:
- Hammer
- Defined in:
- lib/hammer/shell.rb
Overview
ANSI color/output helpers. Mixed into command instances; also callable directly as ‘Hammer::Shell.say(…)`.
Defined Under Namespace
Classes: SayProxy
Class Method Summary collapse
- .ask(prompt, default: nil) ⇒ Object
-
.choose(prompt, items) ⇒ Object
Arrow-key picker.
-
.choose_numbered(items) ⇒ Object
Fallback for non-TTY stdin (pipes, tests).
- .color!(value) ⇒ Object
- .color? ⇒ Boolean
-
.error(text) ⇒ Object
Raise a controlled Hammer::Error.
- .paint(text, color = nil) ⇒ Object
-
.print_error(text) ⇒ Object
Print a red [error] line to stderr (does not exit).
-
.say(text = nil, color = nil) ⇒ Object
‘say` with no args returns a proxy so you can write `say.cyan ’hi’‘.
-
.sh(cmd) ⇒ Object
Run a shell command.
- .yes?(prompt) ⇒ Boolean
Class Method Details
.ask(prompt, default: nil) ⇒ Object
67 68 69 70 71 72 73 74 |
# File 'lib/hammer/shell.rb', line 67 def ask(prompt, default: nil) suffix = default ? " [#{default}]" : '' print paint("#{prompt}#{suffix}: ", :cyan) line = $stdin.gets return default if line.nil? line = line.chomp line.empty? ? default : line end |
.choose(prompt, items) ⇒ Object
Arrow-key picker. Returns the chosen index, or nil on cancel (ESC, Ctrl-C, q). Non-TTY input falls back to a numbered prompt so this stays scriptable.
idx = choose 'Pick env', %w[dev staging prod]
say.green "chose #{ %w[dev staging prod][idx] }" if idx
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/hammer/shell.rb', line 88 def choose(prompt, items) items = items.to_a error 'choose needs at least one item' if items.empty? say.cyan prompt return choose_numbered(items) unless $stdin.tty? && $stdin.respond_to?(:raw) selected = 0 redraw = lambda do |highlight = :cyan| items.each_with_index do |item, i| puts(i == selected ? paint("> #{item}", highlight) : " #{item}") end end redraw.call $stdout.print "\e[?25l" # hide cursor begin $stdin.raw do |io| loop do ch = io.getch case ch when "\r", "\n" # Collapse the list to the chosen line, in green. $stdout.print "\e[#{items.size}A\e[J" puts paint("> #{items[selected]}", :green) return selected when "\x03", 'q' # Ctrl-C, q $stdout.print "\e[#{items.size}A\e[J" return nil when "\e" # ESC may stand alone or start an arrow sequence \e[A / \e[B. if IO.select([io], nil, nil, 0.01) && io.getch == '[' case io.getch when 'A' then selected = (selected - 1) % items.size when 'B' then selected = (selected + 1) % items.size end else $stdout.print "\e[#{items.size}A\e[J" return nil end when 'k' then selected = (selected - 1) % items.size when 'j' then selected = (selected + 1) % items.size end $stdout.print "\e[#{items.size}A\e[J" redraw.call end end ensure $stdout.print "\e[?25h" # show cursor end end |
.choose_numbered(items) ⇒ Object
Fallback for non-TTY stdin (pipes, tests). Returns the index or nil.
142 143 144 145 146 147 148 149 |
# File 'lib/hammer/shell.rb', line 142 def choose_numbered(items) items.each_with_index { |item, i| puts " #{i + 1}) #{item}" } print paint("select [1-#{items.size}]: ", :cyan) line = $stdin.gets return nil if line.nil? idx = line.strip.to_i - 1 idx.between?(0, items.size - 1) ? idx : nil end |
.color!(value) ⇒ Object
20 21 22 |
# File 'lib/hammer/shell.rb', line 20 def color!(value) @color = value end |
.color? ⇒ Boolean
15 16 17 18 |
# File 'lib/hammer/shell.rb', line 15 def color? return @color if defined?(@color) @color = $stdout.tty? && ENV['NO_COLOR'].nil? end |
.error(text) ⇒ Object
Raise a controlled Hammer::Error. If unhandled, the dispatcher prints the message in red and exits 1 - no backtrace, no help spam.
error 'config file missing' unless File.exist?(path)
57 58 59 |
# File 'lib/hammer/shell.rb', line 57 def error(text) raise Hammer::Error, text end |
.paint(text, color = nil) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/hammer/shell.rb', line 24 def paint(text, color = nil) if color && !COLORS.key?(color) raise Hammer::Error, "unknown color #{color.inspect} (valid: #{COLORS.keys.join(', ')})" end return text.to_s unless color? && color "\e[#{COLORS[color]}m#{text}\e[0m" end |
.print_error(text) ⇒ Object
Print a red [error] line to stderr (does not exit). Used internally by the dispatcher to render Hammer::Error messages.
63 64 65 |
# File 'lib/hammer/shell.rb', line 63 def print_error(text) warn paint("[error] #{text}", :red) end |
.say(text = nil, color = nil) ⇒ Object
‘say` with no args returns a proxy so you can write `say.cyan ’hi’‘. `say(”)` still prints a blank line; `say(’x’, :cyan)‘ is unchanged.
34 35 36 37 |
# File 'lib/hammer/shell.rb', line 34 def say(text = nil, color = nil) return SayProxy.new if text.nil? puts paint(text, color) end |
.sh(cmd) ⇒ Object
Run a shell command. Echoes the command in gray, raises Hammer::Error on non-zero exit. Returns true on success.
153 154 155 156 157 |
# File 'lib/hammer/shell.rb', line 153 def sh(cmd) say "$ #{cmd}", :gray error "command failed: #{cmd}" unless system(cmd) true end |
.yes?(prompt) ⇒ Boolean
76 77 78 79 80 |
# File 'lib/hammer/shell.rb', line 76 def yes?(prompt) answer = ask("#{prompt} (y/N)") return false if answer.nil? answer.to_s.strip.downcase.start_with?('y') end |