dev-ui 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -7
- data/Gemfile.lock +10 -11
- data/README.md +13 -1
- data/Rakefile +19 -0
- data/dev-ui.gemspec +4 -4
- data/dev.yml +8 -1
- data/lib/dev/ui.rb +1 -2
- data/lib/dev/ui/formatter.rb +2 -2
- data/lib/dev/ui/frame.rb +16 -16
- data/lib/dev/ui/prompt.rb +21 -6
- data/lib/dev/ui/prompt/interactive_options.rb +169 -0
- data/lib/dev/ui/spinner/spin_group.rb +1 -1
- data/lib/dev/ui/terminal.rb +1 -1
- data/lib/dev/ui/version.rb +1 -1
- metadata +7 -5
- data/bin/testunit +0 -20
- data/lib/dev/ui/interactive_prompt.rb +0 -150
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32b6a7470b4f74f2448604df24a809d4dd5b6176
|
4
|
+
data.tar.gz: 47da2a0c8007c5232d7e447ba74406586fa740f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51d64a4852b8b4981c778be9d1e0bbbd98bbe58a18917af5fcf2c05f2f53494380726a9ddb1c48e10481afbeac7ea99a9101562cb9b5464fcbddeef2f4debb40
|
7
|
+
data.tar.gz: 3749f235fc1b4e30ac3836aec348f8848172ef6d4ae35299c30b6dfc291caab6e4dca9afa891d5bde83c5e3d21dacb736f34c95d370af5a1d900e407cae86813
|
data/.rubocop.yml
CHANGED
@@ -2,13 +2,7 @@ inherit_from:
|
|
2
2
|
- http://shopify.github.io/ruby-style-guide/rubocop.yml
|
3
3
|
|
4
4
|
AllCops:
|
5
|
-
|
6
|
-
- 'vendor/**/*'
|
7
|
-
TargetRubyVersion: 2.0
|
8
|
-
|
9
|
-
# This doesn't understand that <<~ doesn't exist in 2.0
|
10
|
-
Style/IndentHeredoc:
|
11
|
-
Enabled: false
|
5
|
+
TargetRubyVersion: 2.1
|
12
6
|
|
13
7
|
# This doesn't take into account retrying from an exception
|
14
8
|
Lint/HandleExceptions:
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dev-ui (0.1.
|
4
|
+
dev-ui (0.1.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -20,22 +20,21 @@ GEM
|
|
20
20
|
ruby-progressbar
|
21
21
|
mocha (1.2.1)
|
22
22
|
metaclass (~> 0.0.1)
|
23
|
-
parallel (1.
|
24
|
-
parser (2.4.0.
|
25
|
-
ast (~> 2.
|
23
|
+
parallel (1.12.0)
|
24
|
+
parser (2.4.0.2)
|
25
|
+
ast (~> 2.3)
|
26
26
|
powerpack (0.1.1)
|
27
|
-
rainbow (
|
28
|
-
rake
|
27
|
+
rainbow (3.0.0)
|
29
28
|
rake (10.5.0)
|
30
|
-
rubocop (0.
|
29
|
+
rubocop (0.52.0)
|
31
30
|
parallel (~> 1.10)
|
32
|
-
parser (>= 2.
|
31
|
+
parser (>= 2.4.0.2, < 3.0)
|
33
32
|
powerpack (~> 0.1)
|
34
|
-
rainbow (>=
|
33
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
34
|
ruby-progressbar (~> 1.7)
|
36
35
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
37
|
-
ruby-progressbar (1.
|
38
|
-
unicode-display_width (1.
|
36
|
+
ruby-progressbar (1.9.0)
|
37
|
+
unicode-display_width (1.3.0)
|
39
38
|
|
40
39
|
PLATFORMS
|
41
40
|
ruby
|
data/README.md
CHANGED
@@ -47,7 +47,6 @@ Prompt user with options and ask them to choose. Can answer using arrow keys, nu
|
|
47
47
|
|
48
48
|
```ruby
|
49
49
|
Dev::UI.ask('What language/framework do you use?', options: %w(rails go ruby python))
|
50
|
-
Dev::UI::InteractivePrompt.call(%w(rails go ruby python))
|
51
50
|
```
|
52
51
|
|
53
52
|
![Interactive Prompt](https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif)
|
@@ -150,3 +149,16 @@ end
|
|
150
149
|
Output:
|
151
150
|
|
152
151
|
![Example Output](https://user-images.githubusercontent.com/3074765/33797758-7a54c7cc-dcdb-11e7-918e-a47c9689f068.gif)
|
152
|
+
|
153
|
+
## Development
|
154
|
+
|
155
|
+
- Run tests using `bundle exec rake test`. All code should be tested.
|
156
|
+
- No code, outside of development and tests needs, should use dependencies. This is a self contained library
|
157
|
+
|
158
|
+
## Contributing
|
159
|
+
|
160
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/dev-ui.
|
161
|
+
|
162
|
+
## License
|
163
|
+
|
164
|
+
The code is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'dev/ui'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
TEST_ROOT = File.expand_path('../test', __FILE__)
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs += ["test"]
|
10
|
+
t.test_files = FileList[File.join(TEST_ROOT, '**', '*_test.rb')]
|
11
|
+
t.verbose = false
|
12
|
+
t.warning = false
|
13
|
+
end
|
14
|
+
|
15
|
+
RuboCop::RakeTask.new(:style) do |t|
|
16
|
+
t.options = ['--display-cop-names']
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: [:test]
|
data/dev-ui.gemspec
CHANGED
@@ -6,11 +6,11 @@ require "dev/ui/version"
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "dev-ui"
|
8
8
|
spec.version = Dev::UI::VERSION
|
9
|
-
spec.authors = ["Burke Libbey", "Julian Nadeau"]
|
10
|
-
spec.email = ["burke.libbey@shopify.com", "julian.nadeau@shopify.com"]
|
9
|
+
spec.authors = ["Burke Libbey", "Julian Nadeau", "Lisa Ugray"]
|
10
|
+
spec.email = ["burke.libbey@shopify.com", "julian.nadeau@shopify.com", "lisa.ugray@shopify.com"]
|
11
11
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
12
|
+
spec.summary = 'Terminal UI framework'
|
13
|
+
spec.description = 'Terminal UI framework'
|
14
14
|
spec.homepage = "https://github.com/shopify/dev-ui"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
data/dev.yml
CHANGED
data/lib/dev/ui.rb
CHANGED
@@ -5,7 +5,6 @@ module Dev
|
|
5
5
|
autoload :Color, 'dev/ui/color'
|
6
6
|
autoload :Box, 'dev/ui/box'
|
7
7
|
autoload :Frame, 'dev/ui/frame'
|
8
|
-
autoload :InteractivePrompt, 'dev/ui/interactive_prompt'
|
9
8
|
autoload :Progress, 'dev/ui/progress'
|
10
9
|
autoload :Prompt, 'dev/ui/prompt'
|
11
10
|
autoload :Terminal, 'dev/ui/terminal'
|
@@ -140,7 +139,7 @@ module Dev
|
|
140
139
|
yield
|
141
140
|
ensure
|
142
141
|
if file_descriptor = Dev::UI::StdoutRouter.duplicate_output_to
|
143
|
-
file_descriptor.close
|
142
|
+
file_descriptor.close
|
144
143
|
Dev::UI::StdoutRouter.duplicate_output_to = nil
|
145
144
|
end
|
146
145
|
end
|
data/lib/dev/ui/formatter.rb
CHANGED
data/lib/dev/ui/frame.rb
CHANGED
@@ -29,7 +29,7 @@ module Dev
|
|
29
29
|
# * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
|
30
30
|
#
|
31
31
|
# ==== Example
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# ===== Block Form (Assumes +Dev::UI::StdoutRouter.enable+ has been called)
|
34
34
|
#
|
35
35
|
# Dev::UI::Frame.open('Open') { puts 'hi' }
|
@@ -38,7 +38,7 @@ module Dev
|
|
38
38
|
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
39
39
|
# ┃ hi
|
40
40
|
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
|
41
|
-
#
|
41
|
+
#
|
42
42
|
# ===== Blockless Form
|
43
43
|
#
|
44
44
|
# Dev::UI::Frame.open('Open')
|
@@ -46,7 +46,7 @@ module Dev
|
|
46
46
|
# Output:
|
47
47
|
# ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
48
48
|
#
|
49
|
-
#
|
49
|
+
#
|
50
50
|
def open(
|
51
51
|
text,
|
52
52
|
color: DEFAULT_FRAME_COLOR,
|
@@ -80,7 +80,7 @@ module Dev
|
|
80
80
|
begin
|
81
81
|
success = false
|
82
82
|
success = yield
|
83
|
-
rescue
|
83
|
+
rescue
|
84
84
|
closed = true
|
85
85
|
t_diff = timing ? (Time.now.to_f - t_start) : nil
|
86
86
|
close(failure_text, color: :red, elapsed: t_diff)
|
@@ -118,7 +118,7 @@ module Dev
|
|
118
118
|
# Output:
|
119
119
|
# ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
120
120
|
#
|
121
|
-
#
|
121
|
+
#
|
122
122
|
def close(text, color: DEFAULT_FRAME_COLOR, elapsed: nil)
|
123
123
|
color = Dev::UI.resolve_color(color)
|
124
124
|
|
@@ -188,7 +188,7 @@ module Dev
|
|
188
188
|
pfx
|
189
189
|
end
|
190
190
|
|
191
|
-
# Override a color for a given thread.
|
191
|
+
# Override a color for a given thread.
|
192
192
|
#
|
193
193
|
# ==== Attributes
|
194
194
|
#
|
@@ -202,7 +202,7 @@ module Dev
|
|
202
202
|
Thread.current[:devui_frame_color_override] = prev
|
203
203
|
end
|
204
204
|
|
205
|
-
# The width of a prefix given the number of Frames in the stack
|
205
|
+
# The width of a prefix given the number of Frames in the stack
|
206
206
|
#
|
207
207
|
def prefix_width
|
208
208
|
w = FrameStack.items.size
|
@@ -254,15 +254,15 @@ module Dev
|
|
254
254
|
# Jumping around the line can cause some unwanted flashes
|
255
255
|
o << Dev::UI::ANSI.hide_cursor
|
256
256
|
|
257
|
-
if is_ci
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
257
|
+
o << if is_ci
|
258
|
+
# In CI, we can't use absolute horizontal positions because of timestamps.
|
259
|
+
# So we move around the line by offset from this cursor position.
|
260
|
+
Dev::UI::ANSI.cursor_save
|
261
|
+
else
|
262
|
+
# Outside of CI, we reset to column 1 so that things like ^C don't
|
263
|
+
# cause output misformatting.
|
264
|
+
"\r"
|
265
|
+
end
|
266
266
|
|
267
267
|
o << color.code
|
268
268
|
o << Dev::UI::Box::Heavy::HORZ * termwidth # draw a full line
|
data/lib/dev/ui/prompt.rb
CHANGED
@@ -4,6 +4,9 @@ require 'readline'
|
|
4
4
|
module Dev
|
5
5
|
module UI
|
6
6
|
module Prompt
|
7
|
+
autoload :InteractiveOptions, 'dev/ui/prompt/interactive_options'
|
8
|
+
private_constant :InteractiveOptions
|
9
|
+
|
7
10
|
class << self
|
8
11
|
# Ask a user a question with either free form answer or a set of answers
|
9
12
|
# Do not use this method for yes/no questions. Use +confirm+
|
@@ -12,7 +15,7 @@ module Dev
|
|
12
15
|
# * Handles free form answers (options are nil)
|
13
16
|
# * Handles default answers for free form text
|
14
17
|
# * Handles file auto completion for file input
|
15
|
-
# * Handles interactively choosing answers using +
|
18
|
+
# * Handles interactively choosing answers using +InteractiveOptions+
|
16
19
|
#
|
17
20
|
# https://user-images.githubusercontent.com/3074765/33799822-47f23302-dd01-11e7-82f3-9072a5a5f611.png
|
18
21
|
#
|
@@ -22,7 +25,7 @@ module Dev
|
|
22
25
|
#
|
23
26
|
# ==== Options
|
24
27
|
#
|
25
|
-
# * +:options+ - Options to ask the user. Will use +
|
28
|
+
# * +:options+ - Options to ask the user. Will use +InteractiveOptions+ to do so
|
26
29
|
# * +:default+ - The default answer to the question (e.g. they just press enter and don't input anything)
|
27
30
|
# * +:is_file+ - Tells the input to use file auto-completion (tab completion)
|
28
31
|
# * +:allow_empty+ - Allows the answer to be empty
|
@@ -62,8 +65,21 @@ module Dev
|
|
62
65
|
puts_question(question)
|
63
66
|
end
|
64
67
|
|
65
|
-
|
68
|
+
# Present the user with options
|
69
|
+
if options
|
70
|
+
resp = InteractiveOptions.call(options)
|
71
|
+
|
72
|
+
# Clear the line, and reset the question to include the answer
|
73
|
+
print(ANSI.previous_line + ANSI.end_of_line + ' ')
|
74
|
+
print(ANSI.cursor_save)
|
75
|
+
print(' ' * Dev::UI::Terminal.width)
|
76
|
+
print(ANSI.cursor_restore)
|
77
|
+
puts_question("#{question} (You chose: {{italic:#{resp}}})")
|
66
78
|
|
79
|
+
return resp
|
80
|
+
end
|
81
|
+
|
82
|
+
# Ask a free form question
|
67
83
|
loop do
|
68
84
|
line = readline(is_file: is_file)
|
69
85
|
|
@@ -83,12 +99,11 @@ module Dev
|
|
83
99
|
#
|
84
100
|
# ==== Example Usage:
|
85
101
|
#
|
86
|
-
#
|
102
|
+
# Confirmation question
|
87
103
|
# Dev::UI::Prompt.confirm('Is the sky blue?')
|
88
104
|
#
|
89
105
|
def confirm(question)
|
90
|
-
|
91
|
-
InteractivePrompt.call(%w(yes no)) == 'yes'
|
106
|
+
ask(question, options: %w(yes no)) == 'yes'
|
92
107
|
end
|
93
108
|
|
94
109
|
private
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
module UI
|
5
|
+
module Prompt
|
6
|
+
class InteractiveOptions
|
7
|
+
# Prompts the user with options
|
8
|
+
# Uses an interactive session to allow the user to pick an answer
|
9
|
+
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
10
|
+
#
|
11
|
+
# https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif
|
12
|
+
#
|
13
|
+
# ==== Example Usage:
|
14
|
+
#
|
15
|
+
# Ask an interactive question
|
16
|
+
# Dev::UI::Prompt::InteractiveOptions.call(%w(rails go python))
|
17
|
+
#
|
18
|
+
def self.call(options)
|
19
|
+
list = new(options)
|
20
|
+
options[list.call - 1]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Initializes a new +InteractiveOptions+
|
24
|
+
# Usually called from +self.call+
|
25
|
+
#
|
26
|
+
# ==== Example Usage:
|
27
|
+
#
|
28
|
+
# Dev::UI::Prompt::InteractiveOptions.new(%w(rails go python))
|
29
|
+
#
|
30
|
+
def initialize(options)
|
31
|
+
@options = options
|
32
|
+
@active = 1
|
33
|
+
@marker = '>'
|
34
|
+
@answer = nil
|
35
|
+
@state = :root
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calls the +InteractiveOptions+ and asks the question
|
39
|
+
# Usually used from +self.call+
|
40
|
+
#
|
41
|
+
def call
|
42
|
+
Dev::UI.raw { print(ANSI.hide_cursor) }
|
43
|
+
while @answer.nil?
|
44
|
+
render_options
|
45
|
+
wait_for_user_input
|
46
|
+
reset_position
|
47
|
+
end
|
48
|
+
clear_output
|
49
|
+
@answer
|
50
|
+
ensure
|
51
|
+
Dev::UI.raw do
|
52
|
+
print(ANSI.show_cursor)
|
53
|
+
puts(ANSI.previous_line + ANSI.end_of_line)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def reset_position
|
60
|
+
# This will put us back at the beginning of the options
|
61
|
+
# When we redraw the options, they will be overwritten
|
62
|
+
Dev::UI.raw do
|
63
|
+
num_lines.times { print(ANSI.previous_line) }
|
64
|
+
print(ANSI.previous_line + ANSI.end_of_line + "\n")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear_output
|
69
|
+
Dev::UI.raw do
|
70
|
+
# Write over all lines with whitespace
|
71
|
+
num_lines.times { puts(' ' * Dev::UI::Terminal.width) }
|
72
|
+
end
|
73
|
+
reset_position
|
74
|
+
end
|
75
|
+
|
76
|
+
def num_lines
|
77
|
+
# @options will be an array of questions but each option can be multi-line
|
78
|
+
# so to get the # of lines, you need to join then split
|
79
|
+
joined_options = @options.join("\n")
|
80
|
+
joined_options.split("\n").reject(&:empty?).size
|
81
|
+
end
|
82
|
+
|
83
|
+
ESC = "\e"
|
84
|
+
|
85
|
+
def up
|
86
|
+
@active = @active - 1 >= 1 ? @active - 1 : @options.length
|
87
|
+
end
|
88
|
+
|
89
|
+
def down
|
90
|
+
@active = @active + 1 <= @options.length ? @active + 1 : 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def select_n(n)
|
94
|
+
@active = n
|
95
|
+
@answer = n
|
96
|
+
end
|
97
|
+
|
98
|
+
def select_bool(char)
|
99
|
+
return unless (@options - %w(yes no)).empty?
|
100
|
+
opt = @options.detect { |o| o.start_with?(char) }
|
101
|
+
@active = @options.index(opt) + 1
|
102
|
+
@answer = @options.index(opt) + 1
|
103
|
+
end
|
104
|
+
|
105
|
+
# rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon
|
106
|
+
def wait_for_user_input
|
107
|
+
char = read_char
|
108
|
+
case @state
|
109
|
+
when :root
|
110
|
+
case char
|
111
|
+
when ESC ; @state = :esc
|
112
|
+
when 'k' ; up
|
113
|
+
when 'j' ; down
|
114
|
+
when ('1'..@options.size.to_s) ; select_n(char.to_i)
|
115
|
+
when 'y', 'n' ; select_bool(char)
|
116
|
+
when " ", "\r", "\n" ; @answer = @active # <enter>
|
117
|
+
when "\u0003" ; raise Interrupt # Ctrl-c
|
118
|
+
end
|
119
|
+
when :esc
|
120
|
+
case char
|
121
|
+
when '[' ; @state = :esc_bracket
|
122
|
+
else ; raise Interrupt # unhandled escape sequence.
|
123
|
+
end
|
124
|
+
when :esc_bracket
|
125
|
+
@state = :root
|
126
|
+
case char
|
127
|
+
when 'A' ; up
|
128
|
+
when 'B' ; down
|
129
|
+
else ; raise Interrupt # unhandled escape sequence.
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
# rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon
|
134
|
+
|
135
|
+
def read_char
|
136
|
+
raw_tty! { $stdin.getc.chr }
|
137
|
+
rescue IOError
|
138
|
+
"\e"
|
139
|
+
end
|
140
|
+
|
141
|
+
def raw_tty!
|
142
|
+
if ENV['TEST'] || !$stdin.tty?
|
143
|
+
yield
|
144
|
+
else
|
145
|
+
$stdin.raw { yield }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def render_options
|
150
|
+
@options.each_with_index do |choice, index|
|
151
|
+
num = index + 1
|
152
|
+
message = " #{num}."
|
153
|
+
message += choice.split("\n").map { |l| " {{bold:#{l}}}" }.join("\n")
|
154
|
+
|
155
|
+
if num == @active
|
156
|
+
message = message.split("\n").map.with_index do |l, idx|
|
157
|
+
idx == 0 ? "{{blue:> #{l.strip}}}" : "{{blue:>#{l.strip}}}"
|
158
|
+
end.join("\n")
|
159
|
+
end
|
160
|
+
|
161
|
+
Dev::UI.with_frame_color(:blue) do
|
162
|
+
puts Dev::UI.fmt(message)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/dev/ui/terminal.rb
CHANGED
data/lib/dev/ui/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dev-ui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
- Julian Nadeau
|
9
|
+
- Lisa Ugray
|
9
10
|
autorequire:
|
10
11
|
bindir: exe
|
11
12
|
cert_chain: []
|
12
|
-
date: 2017-12-
|
13
|
+
date: 2017-12-13 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -57,6 +58,7 @@ description: Terminal UI framework
|
|
57
58
|
email:
|
58
59
|
- burke.libbey@shopify.com
|
59
60
|
- julian.nadeau@shopify.com
|
61
|
+
- lisa.ugray@shopify.com
|
60
62
|
executables: []
|
61
63
|
extensions: []
|
62
64
|
extra_rdoc_files: []
|
@@ -68,8 +70,8 @@ files:
|
|
68
70
|
- Gemfile.lock
|
69
71
|
- LICENSE.txt
|
70
72
|
- README.md
|
73
|
+
- Rakefile
|
71
74
|
- bin/console
|
72
|
-
- bin/testunit
|
73
75
|
- dev-ui.gemspec
|
74
76
|
- dev.yml
|
75
77
|
- lib/dev/ui.rb
|
@@ -79,9 +81,9 @@ files:
|
|
79
81
|
- lib/dev/ui/formatter.rb
|
80
82
|
- lib/dev/ui/frame.rb
|
81
83
|
- lib/dev/ui/glyph.rb
|
82
|
-
- lib/dev/ui/interactive_prompt.rb
|
83
84
|
- lib/dev/ui/progress.rb
|
84
85
|
- lib/dev/ui/prompt.rb
|
86
|
+
- lib/dev/ui/prompt/interactive_options.rb
|
85
87
|
- lib/dev/ui/spinner.rb
|
86
88
|
- lib/dev/ui/spinner/async.rb
|
87
89
|
- lib/dev/ui/spinner/spin_group.rb
|
@@ -108,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
110
|
version: '0'
|
109
111
|
requirements: []
|
110
112
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.
|
113
|
+
rubygems_version: 2.5.2.1
|
112
114
|
signing_key:
|
113
115
|
specification_version: 4
|
114
116
|
summary: Terminal UI framework
|
data/bin/testunit
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby --disable-gems
|
2
|
-
|
3
|
-
root = File.expand_path('../..', __FILE__)
|
4
|
-
DEV_TEST_ROOT = root + '/test'
|
5
|
-
|
6
|
-
$LOAD_PATH.unshift(DEV_TEST_ROOT)
|
7
|
-
|
8
|
-
def test_files
|
9
|
-
Dir.glob(DEV_TEST_ROOT + "/**/*_test.rb")
|
10
|
-
end
|
11
|
-
|
12
|
-
if ARGV.empty?
|
13
|
-
test_files.each { |f| require(f) }
|
14
|
-
exit 0
|
15
|
-
end
|
16
|
-
|
17
|
-
# A list of files is presumed to be specified
|
18
|
-
ARGV.each do |a|
|
19
|
-
require a.sub(%r{^test/}, '').sub(%r{^misc/gatekeeper/test/}, '')
|
20
|
-
end
|
@@ -1,150 +0,0 @@
|
|
1
|
-
require 'io/console'
|
2
|
-
|
3
|
-
module Dev
|
4
|
-
module UI
|
5
|
-
class InteractivePrompt
|
6
|
-
# Prompts the user with options
|
7
|
-
# Uses an interactive session to allow the user to pick an answer
|
8
|
-
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
9
|
-
#
|
10
|
-
# https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif
|
11
|
-
#
|
12
|
-
# ==== Example Usage:
|
13
|
-
#
|
14
|
-
# Ask an interactive question
|
15
|
-
# Dev::UI::InteractivePrompt.call(%w(rails go python))
|
16
|
-
#
|
17
|
-
def self.call(options)
|
18
|
-
list = new(options)
|
19
|
-
options[list.call - 1]
|
20
|
-
end
|
21
|
-
|
22
|
-
# Initializes a new +InteractivePrompt+
|
23
|
-
# Usually called from +self.call+
|
24
|
-
#
|
25
|
-
# ==== Example Usage:
|
26
|
-
#
|
27
|
-
# Dev::UI::InteractivePrompt.new(%w(rails go python))
|
28
|
-
#
|
29
|
-
def initialize(options)
|
30
|
-
@options = options
|
31
|
-
@active = 1
|
32
|
-
@marker = '>'
|
33
|
-
@answer = nil
|
34
|
-
@state = :root
|
35
|
-
end
|
36
|
-
|
37
|
-
# Calls the +InteractivePrompt+ and asks the question
|
38
|
-
# Usually used from +self.call+
|
39
|
-
#
|
40
|
-
def call
|
41
|
-
Dev::UI.raw { print(ANSI.hide_cursor) }
|
42
|
-
while @answer.nil?
|
43
|
-
render_options
|
44
|
-
wait_for_user_input
|
45
|
-
|
46
|
-
# This will put us back at the beginning of the options
|
47
|
-
# When we redraw the options, they will be overwritten
|
48
|
-
Dev::UI.raw do
|
49
|
-
num_lines = @options.join("\n").split("\n").reject(&:empty?).size
|
50
|
-
num_lines.times { print(ANSI.previous_line) }
|
51
|
-
print(ANSI.previous_line + ANSI.end_of_line + "\n")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
render_options
|
55
|
-
@answer
|
56
|
-
ensure
|
57
|
-
Dev::UI.raw do
|
58
|
-
print(ANSI.show_cursor)
|
59
|
-
puts(ANSI.previous_line + ANSI.end_of_line)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
ESC = "\e"
|
66
|
-
|
67
|
-
def up
|
68
|
-
@active = @active - 1 >= 1 ? @active - 1 : @options.length
|
69
|
-
end
|
70
|
-
|
71
|
-
def down
|
72
|
-
@active = @active + 1 <= @options.length ? @active + 1 : 1
|
73
|
-
end
|
74
|
-
|
75
|
-
def select_n(n)
|
76
|
-
@active = n
|
77
|
-
@answer = n
|
78
|
-
end
|
79
|
-
|
80
|
-
def select_bool(char)
|
81
|
-
return unless (@options - %w(yes no)).empty?
|
82
|
-
opt = @options.detect { |o| o.start_with?(char) }
|
83
|
-
@active = @options.index(opt) + 1
|
84
|
-
@answer = @options.index(opt) + 1
|
85
|
-
end
|
86
|
-
|
87
|
-
# rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon
|
88
|
-
def wait_for_user_input
|
89
|
-
char = read_char
|
90
|
-
case @state
|
91
|
-
when :root
|
92
|
-
case char
|
93
|
-
when ESC ; @state = :esc
|
94
|
-
when 'k' ; up
|
95
|
-
when 'j' ; down
|
96
|
-
when ('1'..@options.size.to_s) ; select_n(char.to_i)
|
97
|
-
when 'y', 'n' ; select_bool(char)
|
98
|
-
when " ", "\r", "\n" ; @answer = @active # <enter>
|
99
|
-
when "\u0003" ; raise Interrupt # Ctrl-c
|
100
|
-
end
|
101
|
-
when :esc
|
102
|
-
case char
|
103
|
-
when '[' ; @state = :esc_bracket
|
104
|
-
else ; raise Interrupt # unhandled escape sequence.
|
105
|
-
end
|
106
|
-
when :esc_bracket
|
107
|
-
@state = :root
|
108
|
-
case char
|
109
|
-
when 'A' ; up
|
110
|
-
when 'B' ; down
|
111
|
-
else ; raise Interrupt # unhandled escape sequence.
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
# rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon
|
116
|
-
|
117
|
-
def read_char
|
118
|
-
raw_tty! { $stdin.getc.chr }
|
119
|
-
rescue IOError
|
120
|
-
"\e"
|
121
|
-
end
|
122
|
-
|
123
|
-
def raw_tty!
|
124
|
-
if ENV['TEST'] || !$stdin.tty?
|
125
|
-
yield
|
126
|
-
else
|
127
|
-
$stdin.raw { yield }
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def render_options
|
132
|
-
@options.each_with_index do |choice, index|
|
133
|
-
num = index + 1
|
134
|
-
message = " #{num}."
|
135
|
-
message += choice.split("\n").map { |l| " {{bold:#{l}}}" }.join("\n")
|
136
|
-
|
137
|
-
if num == @active
|
138
|
-
message = message.split("\n").map.with_index do |l, idx|
|
139
|
-
idx == 0 ? "{{blue:> #{l.strip}}}" : "{{blue:>#{l.strip}}}"
|
140
|
-
end.join("\n")
|
141
|
-
end
|
142
|
-
|
143
|
-
Dev::UI.with_frame_color(:blue) do
|
144
|
-
puts Dev::UI.fmt(message)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|