clack 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -9
- data/LICENSE +1 -1
- data/README.md +0 -1
- data/lib/clack/core/prompt.rb +49 -1
- data/lib/clack/prompts/password.rb +4 -3
- data/lib/clack/prompts/path.rb +24 -3
- data/lib/clack/prompts/progress.rb +4 -9
- data/lib/clack/version.rb +1 -1
- data/lib/clack.rb +3 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9749ca99273a0ed19da8274041da1b04f7355f523761169b509e3234737be7b
|
|
4
|
+
data.tar.gz: 8a097667af010ba054486e6a22407fefdb021752a9ce018ac2a0489f060f4140
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6aa1f190f08a4fc285e682f6838518951ddc25647eaf034e06bdf7d1b1d06b15b0cc7cb2de3bd4c5ead11adcaea00b7cd8e2c6e8d03f28287b084f187766dd28
|
|
7
|
+
data.tar.gz: f6b11ea693a6ba6440a907c01b25a33ade736d074eaed61095998b873385466ee1c086ad2bb78546287e2e038a56a4cab23f509c678e3c9ecb752bca6c540907
|
data/CHANGELOG.md
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
5
|
+
## [0.1.1]
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Path prompt now correctly rejects paths outside root (fixed boundary check bug)
|
|
9
|
+
- Password backspace properly removes Unicode grapheme clusters, not just bytes
|
|
10
|
+
- Terminal cleanup handles closed output streams gracefully
|
|
11
|
+
- SIGWINCH handler uses explicit signal check instead of rescue
|
|
11
12
|
|
|
12
|
-
###
|
|
13
|
-
-
|
|
13
|
+
### Added
|
|
14
|
+
- Terminal resize support via SIGWINCH signal handling
|
|
14
15
|
|
|
15
16
|
## [0.1.0]
|
|
16
17
|
|
|
17
|
-
Initial release.
|
|
18
|
+
Initial release with full prompt library.
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -10,7 +10,6 @@ A faithful Ruby port of [@clack/prompts](https://github.com/bombshell-dev/clack)
|
|
|
10
10
|
|
|
11
11
|
## Why Clack?
|
|
12
12
|
|
|
13
|
-
- **Zero dependencies** - Pure Ruby, stdlib only
|
|
14
13
|
- **Beautiful by default** - Thoughtfully designed prompts that just look right
|
|
15
14
|
- **Vim-friendly** - Navigate with `hjkl` or arrow keys
|
|
16
15
|
- **Accessible** - Graceful ASCII fallbacks for limited terminals
|
data/lib/clack/core/prompt.rb
CHANGED
|
@@ -27,6 +27,36 @@ module Clack
|
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
29
|
class Prompt
|
|
30
|
+
# Track active prompts for SIGWINCH notification.
|
|
31
|
+
# Signal handler may fire during register/unregister. We can't use
|
|
32
|
+
# .dup (allocates, forbidden in trap context) so we accept a benign
|
|
33
|
+
# race: worst case, a prompt misses one resize notification.
|
|
34
|
+
@active_prompts = []
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
attr_reader :active_prompts
|
|
38
|
+
|
|
39
|
+
# Register a prompt instance for resize notifications
|
|
40
|
+
def register(prompt)
|
|
41
|
+
@active_prompts << prompt
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unregister(prompt)
|
|
45
|
+
@active_prompts.delete(prompt)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Set up SIGWINCH handler (called once on load).
|
|
49
|
+
# Signal handlers must avoid mutex/complex operations.
|
|
50
|
+
def setup_signal_handler
|
|
51
|
+
return if Clack::Environment.windows?
|
|
52
|
+
return unless Signal.list.key?("WINCH")
|
|
53
|
+
|
|
54
|
+
Signal.trap("WINCH") do
|
|
55
|
+
@active_prompts.each(&:request_redraw)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
30
60
|
# @return [Symbol] current state (:initial, :active, :error, :submit, :cancel)
|
|
31
61
|
attr_reader :state
|
|
32
62
|
# @return [Object] the current/final value
|
|
@@ -48,6 +78,13 @@ module Clack
|
|
|
48
78
|
@error_message = nil
|
|
49
79
|
@prev_frame = nil
|
|
50
80
|
@cursor = 0
|
|
81
|
+
@needs_redraw = false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Request a full redraw on next render cycle.
|
|
85
|
+
# Called by SIGWINCH handler when terminal is resized.
|
|
86
|
+
def request_redraw
|
|
87
|
+
@needs_redraw = true
|
|
51
88
|
end
|
|
52
89
|
|
|
53
90
|
# Run the prompt interaction loop.
|
|
@@ -57,6 +94,7 @@ module Clack
|
|
|
57
94
|
#
|
|
58
95
|
# @return [Object, Clack::CANCEL] the submitted value or CANCEL sentinel
|
|
59
96
|
def run
|
|
97
|
+
Prompt.register(self)
|
|
60
98
|
setup_terminal
|
|
61
99
|
render
|
|
62
100
|
@state = :active
|
|
@@ -72,6 +110,7 @@ module Clack
|
|
|
72
110
|
finalize
|
|
73
111
|
(terminal_state? && @state == :cancel) ? CANCEL : @value
|
|
74
112
|
ensure
|
|
113
|
+
Prompt.unregister(self)
|
|
75
114
|
cleanup_terminal
|
|
76
115
|
end
|
|
77
116
|
|
|
@@ -121,9 +160,16 @@ module Clack
|
|
|
121
160
|
end
|
|
122
161
|
|
|
123
162
|
# Render the current frame using differential rendering.
|
|
124
|
-
# Only redraws if the frame content has changed.
|
|
163
|
+
# Only redraws if the frame content has changed or redraw was requested.
|
|
125
164
|
def render
|
|
126
165
|
frame = build_frame
|
|
166
|
+
|
|
167
|
+
# Force redraw if terminal was resized
|
|
168
|
+
if @needs_redraw
|
|
169
|
+
@needs_redraw = false
|
|
170
|
+
@prev_frame = nil
|
|
171
|
+
end
|
|
172
|
+
|
|
127
173
|
return if frame == @prev_frame
|
|
128
174
|
|
|
129
175
|
if @state == :initial
|
|
@@ -176,6 +222,8 @@ module Clack
|
|
|
176
222
|
|
|
177
223
|
def cleanup_terminal
|
|
178
224
|
@output.print Cursor.show
|
|
225
|
+
rescue IOError, SystemCallError
|
|
226
|
+
# Output unavailable - terminal may need manual reset
|
|
179
227
|
end
|
|
180
228
|
|
|
181
229
|
def restore_cursor
|
|
@@ -30,7 +30,8 @@ module Clack
|
|
|
30
30
|
return unless Core::Settings.printable?(key)
|
|
31
31
|
|
|
32
32
|
if Core::Settings.backspace?(key)
|
|
33
|
-
|
|
33
|
+
clusters = @value.grapheme_clusters
|
|
34
|
+
@value = (clusters.length > 0) ? clusters[0..-2].join : ""
|
|
34
35
|
else
|
|
35
36
|
@value += key
|
|
36
37
|
end
|
|
@@ -53,7 +54,7 @@ module Clack
|
|
|
53
54
|
lines << "#{bar}\n"
|
|
54
55
|
lines << "#{symbol_for_state} #{@message}\n"
|
|
55
56
|
|
|
56
|
-
masked = @mask * @value.length
|
|
57
|
+
masked = @mask * @value.grapheme_clusters.length
|
|
57
58
|
display = (@state == :cancel) ? Colors.strikethrough(Colors.dim(masked)) : Colors.dim(masked)
|
|
58
59
|
lines << "#{bar} #{display}\n"
|
|
59
60
|
|
|
@@ -63,7 +64,7 @@ module Clack
|
|
|
63
64
|
private
|
|
64
65
|
|
|
65
66
|
def masked_display
|
|
66
|
-
masked = @mask * @value.length
|
|
67
|
+
masked = @mask * @value.grapheme_clusters.length
|
|
67
68
|
return cursor_block if masked.empty?
|
|
68
69
|
|
|
69
70
|
"#{masked}#{cursor_block}"
|
data/lib/clack/prompts/path.rb
CHANGED
|
@@ -92,6 +92,12 @@ module Clack
|
|
|
92
92
|
def submit_selection
|
|
93
93
|
path = @value.empty? ? @root : resolve_path(@value)
|
|
94
94
|
|
|
95
|
+
unless path_within_root?(path)
|
|
96
|
+
@error_message = "Path must be within #{@root}"
|
|
97
|
+
@state = :error
|
|
98
|
+
return
|
|
99
|
+
end
|
|
100
|
+
|
|
95
101
|
if @validate
|
|
96
102
|
result = @validate.call(path)
|
|
97
103
|
if result
|
|
@@ -138,6 +144,13 @@ module Clack
|
|
|
138
144
|
|
|
139
145
|
def update_suggestions
|
|
140
146
|
base_path = resolve_path(@value)
|
|
147
|
+
|
|
148
|
+
# Only show suggestions for paths within root
|
|
149
|
+
unless path_within_root?(base_path)
|
|
150
|
+
@suggestions = []
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
141
154
|
search_dir = File.directory?(base_path) ? base_path : File.dirname(base_path)
|
|
142
155
|
prefix = File.directory?(base_path) ? "" : File.basename(base_path).downcase
|
|
143
156
|
|
|
@@ -157,10 +170,10 @@ module Clack
|
|
|
157
170
|
|
|
158
171
|
def format_entry(dir, entry)
|
|
159
172
|
full_path = File.join(dir, entry)
|
|
160
|
-
if full_path.start_with?(@root)
|
|
173
|
+
if full_path == @root || full_path.start_with?("#{@root}/")
|
|
161
174
|
# Show relative path without leading ./
|
|
162
175
|
path = full_path[@root.length..]
|
|
163
|
-
path = path.sub(%r{^/}, "")
|
|
176
|
+
path = path.sub(%r{^/}, "")
|
|
164
177
|
path = entry if path.empty?
|
|
165
178
|
else
|
|
166
179
|
path = full_path
|
|
@@ -172,13 +185,21 @@ module Clack
|
|
|
172
185
|
def resolve_path(input)
|
|
173
186
|
return @root if input.empty?
|
|
174
187
|
|
|
175
|
-
if input.start_with?("/")
|
|
188
|
+
path = if input.start_with?("/")
|
|
176
189
|
input
|
|
177
190
|
elsif input.start_with?("~")
|
|
178
191
|
File.expand_path(input)
|
|
179
192
|
else
|
|
180
193
|
File.join(@root, input)
|
|
181
194
|
end
|
|
195
|
+
|
|
196
|
+
# Canonicalize to resolve .. and symlinks
|
|
197
|
+
File.expand_path(path)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def path_within_root?(path)
|
|
201
|
+
expanded = File.expand_path(path)
|
|
202
|
+
expanded == @root || expanded.start_with?("#{@root}/")
|
|
182
203
|
end
|
|
183
204
|
|
|
184
205
|
def visible_suggestions
|
|
@@ -35,7 +35,6 @@ module Clack
|
|
|
35
35
|
@message = message
|
|
36
36
|
@output = output
|
|
37
37
|
@started = false
|
|
38
|
-
@rendered_once = false
|
|
39
38
|
@width = 40
|
|
40
39
|
end
|
|
41
40
|
|
|
@@ -106,17 +105,13 @@ module Clack
|
|
|
106
105
|
def render
|
|
107
106
|
return unless @started
|
|
108
107
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
113
|
-
@rendered_once = true
|
|
114
|
-
@output.puts "#{symbol} #{progress_bar} #{percentage}#{message_text}"
|
|
108
|
+
@output.print "\r\e[2K" # Return to start of line and clear it
|
|
109
|
+
@output.print "#{symbol} #{progress_bar} #{percentage}#{message_text}"
|
|
110
|
+
@output.flush
|
|
115
111
|
end
|
|
116
112
|
|
|
117
113
|
def render_final(state)
|
|
118
|
-
#
|
|
119
|
-
@output.print "\e[1A\e[2K" if @rendered_once
|
|
114
|
+
@output.print "\r\e[2K" # Clear the progress line
|
|
120
115
|
sym = (state == :success) ? Colors.green(Symbols::S_STEP_SUBMIT) : Colors.red(Symbols::S_STEP_CANCEL)
|
|
121
116
|
@output.puts "#{sym} #{@message}"
|
|
122
117
|
end
|
data/lib/clack/version.rb
CHANGED
data/lib/clack.rb
CHANGED