dev-ui 0.1.1 → 0.1.2
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/.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
|

|
@@ -150,3 +149,16 @@ end
|
|
150
149
|
Output:
|
151
150
|
|
152
151
|

|
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
|