dev-ui 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +10 -2
- data/README.md +132 -2
- data/dev.yml +0 -1
- data/lib/dev/ui.rb +82 -4
- data/lib/dev/ui/ansi.rb +67 -6
- data/lib/dev/ui/color.rb +21 -0
- data/lib/dev/ui/formatter.rb +23 -2
- data/lib/dev/ui/frame.rb +128 -10
- data/lib/dev/ui/glyph.rb +43 -18
- data/lib/dev/ui/interactive_prompt.rb +81 -38
- data/lib/dev/ui/progress.rb +42 -17
- data/lib/dev/ui/prompt.rb +54 -1
- data/lib/dev/ui/spinner.rb +28 -148
- data/lib/dev/ui/spinner/async.rb +40 -0
- data/lib/dev/ui/spinner/spin_group.rb +223 -0
- data/lib/dev/ui/stdout_router.rb +3 -2
- data/lib/dev/ui/terminal.rb +3 -0
- data/lib/dev/ui/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb7feeed4cd2083e294dd368521e85d4240cb954
|
4
|
+
data.tar.gz: d87bb0bab774010578bfaed70966e87e8a0222b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b371ef8177c5d3626d75c940d56b3afa40924a70fbdc0bb941b3f33c1e5253eb41118a023c6c3a72e92bd15df2bacc65dc361874cc9f4dd08162d402c9215100
|
7
|
+
data.tar.gz: 0dfc477dd137c74de7a99336478c7c120c3cd394c5e259e2b758a6777a7d1fe1eae0ab859c0728c9a224d80bd0f28601e5c2088488bf4ba4025c32dc0b42005f
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dev-ui (0.1.0)
|
5
|
+
|
1
6
|
GEM
|
2
7
|
remote: https://rubygems.org/
|
3
8
|
specs:
|
@@ -21,7 +26,7 @@ GEM
|
|
21
26
|
powerpack (0.1.1)
|
22
27
|
rainbow (2.2.2)
|
23
28
|
rake
|
24
|
-
rake (
|
29
|
+
rake (10.5.0)
|
25
30
|
rubocop (0.49.1)
|
26
31
|
parallel (~> 1.10)
|
27
32
|
parser (>= 2.3.3.1, < 3.0)
|
@@ -36,12 +41,15 @@ PLATFORMS
|
|
36
41
|
ruby
|
37
42
|
|
38
43
|
DEPENDENCIES
|
44
|
+
bundler (~> 1.15)
|
39
45
|
byebug
|
46
|
+
dev-ui!
|
40
47
|
method_source
|
41
48
|
minitest (>= 5.0.0)
|
42
49
|
minitest-reporters
|
43
50
|
mocha
|
51
|
+
rake (~> 10.0)
|
44
52
|
rubocop
|
45
53
|
|
46
54
|
BUNDLED WITH
|
47
|
-
1.
|
55
|
+
1.16.0
|
data/README.md
CHANGED
@@ -1,3 +1,125 @@
|
|
1
|
+
Dev UI
|
2
|
+
---
|
3
|
+
|
4
|
+
Dev UI is a small framework who's only responsibility is to print pretty to the terminal
|
5
|
+
|
6
|
+
- [Master Documentation](http://www.rubydoc.info/github/Shopify/dev-ui/master/Dev/UI)
|
7
|
+
- [Documentation of the Rubygems version](http://www.rubydoc.info/gems/dev-ui/)
|
8
|
+
- [Rubygems](https://rubygems.org/gems/dev-ui)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
```bash
|
13
|
+
gem install dev-ui
|
14
|
+
```
|
15
|
+
|
16
|
+
or add the following to your Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'dev-ui'
|
20
|
+
```
|
21
|
+
|
22
|
+
In your code, simply add a `require 'dev/ui'`. Most options assume `Dev::UI::StdoutRouter.enable` has been called.
|
23
|
+
|
24
|
+
## Features
|
25
|
+
|
26
|
+
This may not be an exhaustive list. Please check our [documentation](http://www.rubydoc.info/github/Shopify/dev-ui/master/Dev/UI) for more information.
|
27
|
+
|
28
|
+
---
|
29
|
+
|
30
|
+
### Nested framing
|
31
|
+
To handle content flow (see example below)
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Dev::UI::StdoutRouter.enable
|
35
|
+
Dev::UI::Frame.open('Frame 1') do
|
36
|
+
Dev::UI::Frame.open('Frame 2') { puts "inside frame 2" }
|
37
|
+
puts "inside frame 1"
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
![Nested Framing](https://user-images.githubusercontent.com/3074765/33799861-cb5dcb5c-dd01-11e7-977e-6fad38cee08c.png)
|
42
|
+
|
43
|
+
---
|
44
|
+
|
45
|
+
### Interactive Prompts
|
46
|
+
Prompt user with options and ask them to choose. Can answer using arrow keys, numbers, or vim bindings (or y/n for yes/no questions)
|
47
|
+
|
48
|
+
```ruby
|
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
|
+
```
|
52
|
+
|
53
|
+
![Interactive Prompt](https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif)
|
54
|
+
|
55
|
+
---
|
56
|
+
|
57
|
+
### Free form text prompts
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
Dev::UI.ask('Is Dev UI Awesome?', default: 'It is great!')
|
61
|
+
```
|
62
|
+
|
63
|
+
![Free form text prompt](https://user-images.githubusercontent.com/3074765/33799822-47f23302-dd01-11e7-82f3-9072a5a5f611.png)
|
64
|
+
|
65
|
+
---
|
66
|
+
|
67
|
+
### Spinner groups
|
68
|
+
Handle many multi-threaded processes while suppressing output unless there is an issue. Can update title to show state.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
spin_group = Dev::UI::SpinGroup.new
|
72
|
+
spin_group.add('Title') { |spinner| sleep 3.0 }
|
73
|
+
spin_group.add('Title 2') { |spinner| sleep 3.0; spinner.update_title('New Title'); sleep 3.0 }
|
74
|
+
spin_group.wait
|
75
|
+
```
|
76
|
+
|
77
|
+
![Spinner Group](https://user-images.githubusercontent.com/3074765/33798295-d94fd822-dce3-11e7-819b-43e5502d490e.gif)
|
78
|
+
|
79
|
+
---
|
80
|
+
|
81
|
+
### Text Color formatting
|
82
|
+
e.g. `{{red:Red}} {{green:Green}}`
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
puts Dev::UI.fmt "{{red:Red}} {{green:Green}}"
|
86
|
+
```
|
87
|
+
|
88
|
+
![Text Format](https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png)
|
89
|
+
|
90
|
+
---
|
91
|
+
|
92
|
+
### Symbol/Glyph Formatting
|
93
|
+
e.g. `{{*}}` => a yellow ⭑
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
puts Dev::UI.fmt "{{*}} {{x}} {{?}} {{v}}"
|
97
|
+
```
|
98
|
+
|
99
|
+
![Symbol Formatting](https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png)
|
100
|
+
|
101
|
+
---
|
102
|
+
|
103
|
+
### Progress Bar
|
104
|
+
|
105
|
+
Show progress of a process or operation.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Dev::UI::Progress.progress do |bar|
|
109
|
+
100.times do
|
110
|
+
bar.tick
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
![Progress Bar](https://user-images.githubusercontent.com/3074765/33799794-cc4c940e-dd00-11e7-9bdc-90f77ec9167c.gif)
|
116
|
+
|
117
|
+
---
|
118
|
+
|
119
|
+
## Example Usage
|
120
|
+
|
121
|
+
The following code makes use of nested-framing, multi-threaded spinners, formatted text, and more.
|
122
|
+
|
1
123
|
```ruby
|
2
124
|
require 'dev/ui'
|
3
125
|
|
@@ -7,16 +129,24 @@ Dev::UI::Frame.open('{{*}} {{bold:a}}', color: :green) do
|
|
7
129
|
Dev::UI::Frame.open('{{i}} b', color: :magenta) do
|
8
130
|
Dev::UI::Frame.open('{{?}} c', color: :cyan) do
|
9
131
|
sg = Dev::UI::SpinGroup.new
|
10
|
-
sg.add('wow')
|
132
|
+
sg.add('wow') do |spinner|
|
133
|
+
sleep(2.5)
|
134
|
+
spinner.update_title('second round!')
|
135
|
+
sleep (1.0)
|
136
|
+
end
|
11
137
|
sg.add('such spin') { sleep(1.6) }
|
12
138
|
sg.add('many glyph') { sleep(2.0) }
|
13
139
|
sg.wait
|
14
140
|
end
|
15
141
|
end
|
16
142
|
Dev::UI::Frame.divider('{{v}} lol')
|
17
|
-
puts 'words'
|
143
|
+
puts Dev::UI.fmt '{{info:words}} {{red:oh no!}} {{green:success!}}'
|
18
144
|
sg = Dev::UI::SpinGroup.new
|
19
145
|
sg.add('more spins') { sleep(0.5) ; raise 'oh no' }
|
20
146
|
sg.wait
|
21
147
|
end
|
22
148
|
```
|
149
|
+
|
150
|
+
Output:
|
151
|
+
|
152
|
+
![Example Output](https://user-images.githubusercontent.com/3074765/33797758-7a54c7cc-dcdb-11e7-918e-a47c9689f068.gif)
|
data/lib/dev/ui.rb
CHANGED
@@ -12,16 +12,27 @@ module Dev
|
|
12
12
|
autoload :Formatter, 'dev/ui/formatter'
|
13
13
|
autoload :Spinner, 'dev/ui/spinner'
|
14
14
|
|
15
|
-
|
16
|
-
# TODO: this, better
|
15
|
+
# Convenience accessor to +Dev::UI::Spinner::SpinGroup+
|
17
16
|
SpinGroup = Spinner::SpinGroup
|
18
17
|
|
19
|
-
#
|
18
|
+
# Glyph resolution using +Dev::UI::Glyph.lookup+
|
19
|
+
# Look at the method signature for +Glyph.lookup+ for more details
|
20
|
+
#
|
21
|
+
# ==== Attributes
|
22
|
+
#
|
23
|
+
# * +handle+ - handle of the glyph to resolve
|
24
|
+
#
|
20
25
|
def self.glyph(handle)
|
21
26
|
Dev::UI::Glyph.lookup(handle)
|
22
27
|
end
|
23
28
|
|
24
|
-
#
|
29
|
+
# Color resolution using +Dev::UI::Color.lookup+
|
30
|
+
# Will lookup using +Color.lookup+ if a symbol, otherwise we assume it is a valid color and return it
|
31
|
+
#
|
32
|
+
# ==== Attributes
|
33
|
+
#
|
34
|
+
# * +input+ - color to resolve
|
35
|
+
#
|
25
36
|
def self.resolve_color(input)
|
26
37
|
case input
|
27
38
|
when Symbol
|
@@ -31,35 +42,96 @@ module Dev
|
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
45
|
+
# Conviencence Method for +Dev::UI::Prompt.confirm+
|
46
|
+
#
|
47
|
+
# ==== Attributes
|
48
|
+
#
|
49
|
+
# * +question+ - question to confirm
|
50
|
+
#
|
34
51
|
def self.confirm(question)
|
35
52
|
Dev::UI::Prompt.confirm(question)
|
36
53
|
end
|
37
54
|
|
55
|
+
# Conviencence Method for +Dev::UI::Prompt.ask+
|
56
|
+
#
|
57
|
+
# ==== Attributes
|
58
|
+
#
|
59
|
+
# * +question+ - question to ask
|
60
|
+
# * +kwargs+ - arugments for +Prompt.ask+
|
61
|
+
#
|
38
62
|
def self.ask(question, **kwargs)
|
39
63
|
Dev::UI::Prompt.ask(question, **kwargs)
|
40
64
|
end
|
41
65
|
|
66
|
+
# Conviencence Method to resolve text using +Dev::UI::Formatter.format+
|
67
|
+
# Check +Dev::UI::Formatter::SGR_MAP+ for available formatting options
|
68
|
+
#
|
69
|
+
# ==== Attributes
|
70
|
+
#
|
71
|
+
# * +input+ - input to format
|
72
|
+
#
|
42
73
|
def self.resolve_text(input)
|
43
74
|
return input if input.nil?
|
44
75
|
Dev::UI::Formatter.new(input).format
|
45
76
|
end
|
46
77
|
|
78
|
+
# Conviencence Method to format text using +Dev::UI::Formatter.format+
|
79
|
+
# Check +Dev::UI::Formatter::SGR_MAP+ for available formatting options
|
80
|
+
#
|
81
|
+
# https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
|
82
|
+
# https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png
|
83
|
+
#
|
84
|
+
# ==== Attributes
|
85
|
+
#
|
86
|
+
# * +input+ - input to format
|
87
|
+
#
|
88
|
+
# ==== Options
|
89
|
+
#
|
90
|
+
# * +enable_color+ - should color be used? default to true
|
91
|
+
#
|
47
92
|
def self.fmt(input, enable_color: true)
|
48
93
|
Dev::UI::Formatter.new(input).format(enable_color: enable_color)
|
49
94
|
end
|
50
95
|
|
96
|
+
# Conviencence Method for +Dev::UI::Frame.open+
|
97
|
+
#
|
98
|
+
# ==== Attributes
|
99
|
+
#
|
100
|
+
# * +args+ - arguments for +Frame.open+
|
101
|
+
# * +block+ - block for +Frame.open+
|
102
|
+
#
|
51
103
|
def self.frame(*args, &block)
|
52
104
|
Dev::UI::Frame.open(*args, &block)
|
53
105
|
end
|
54
106
|
|
107
|
+
# Conviencence Method for +Dev::UI::Spinner.spin+
|
108
|
+
#
|
109
|
+
# ==== Attributes
|
110
|
+
#
|
111
|
+
# * +args+ - arguments for +Spinner.open+
|
112
|
+
# * +block+ - block for +Spinner.open+
|
113
|
+
#
|
55
114
|
def self.spinner(*args, &block)
|
56
115
|
Dev::UI::Spinner.spin(*args, &block)
|
57
116
|
end
|
58
117
|
|
118
|
+
# Conviencence Method to override frame color using +Dev::UI::Frame.with_frame_color+
|
119
|
+
#
|
120
|
+
# ==== Attributes
|
121
|
+
#
|
122
|
+
# * +color+ - color to override to
|
123
|
+
# * +block+ - block for +Frame.with_frame_color_override+
|
124
|
+
#
|
59
125
|
def self.with_frame_color(color, &block)
|
60
126
|
Dev::UI::Frame.with_frame_color_override(color, &block)
|
61
127
|
end
|
62
128
|
|
129
|
+
# Duplicate output to a file path
|
130
|
+
#
|
131
|
+
# ==== Attributes
|
132
|
+
#
|
133
|
+
# * +path+ - path to duplicate output to
|
134
|
+
#
|
63
135
|
def self.log_output_to(path)
|
64
136
|
if Dev::UI::StdoutRouter.duplicate_output_to
|
65
137
|
raise "multiple logs not allowed"
|
@@ -73,6 +145,12 @@ module Dev
|
|
73
145
|
end
|
74
146
|
end
|
75
147
|
|
148
|
+
# Disable all framing within a block
|
149
|
+
#
|
150
|
+
# ==== Attributes
|
151
|
+
#
|
152
|
+
# * +block+ - block in which to disable frames
|
153
|
+
#
|
76
154
|
def self.raw
|
77
155
|
prev = Thread.current[:no_devui_frame_inset]
|
78
156
|
Thread.current[:no_devui_frame_inset] = true
|
data/lib/dev/ui/ansi.rb
CHANGED
@@ -7,9 +7,9 @@ module Dev
|
|
7
7
|
|
8
8
|
# ANSI escape sequences (like \x1b[31m) have zero width.
|
9
9
|
# when calculating the padding width, we must exclude them.
|
10
|
-
# This also implements a
|
11
|
-
#
|
12
|
-
#
|
10
|
+
# This also implements a basic version of utf8 character width calculation like
|
11
|
+
# we could get for real from something like utf8proc.
|
12
|
+
#
|
13
13
|
def self.printing_width(str)
|
14
14
|
zwj = false
|
15
15
|
strip_codes(str).codepoints.reduce(0) do |acc, cp|
|
@@ -27,10 +27,23 @@ module Dev
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
# Strips ANSI codes from a str
|
31
|
+
#
|
32
|
+
# ==== Attributes
|
33
|
+
#
|
34
|
+
# - +str+ - The string from which to strip codes
|
35
|
+
#
|
30
36
|
def self.strip_codes(str)
|
31
37
|
str.gsub(/\x1b\[[\d;]+[A-z]|\r/, '')
|
32
38
|
end
|
33
39
|
|
40
|
+
# Returns an ANSI control sequence
|
41
|
+
#
|
42
|
+
# ==== Attributes
|
43
|
+
#
|
44
|
+
# - +args+ - Argument to pass to the ANSI control sequence
|
45
|
+
# - +cmd+ - ANSI control sequence Command
|
46
|
+
#
|
34
47
|
def self.control(args, cmd)
|
35
48
|
ESC + "[" + args + cmd
|
36
49
|
end
|
@@ -42,50 +55,98 @@ module Dev
|
|
42
55
|
|
43
56
|
# Cursor Movement
|
44
57
|
|
58
|
+
# Move the cursor up n lines
|
59
|
+
#
|
60
|
+
# ==== Attributes
|
61
|
+
#
|
62
|
+
# * +n+ - number of lines by which to move the cursor up
|
63
|
+
#
|
45
64
|
def self.cursor_up(n = 1)
|
46
65
|
return '' if n.zero?
|
47
66
|
control(n.to_s, 'A')
|
48
67
|
end
|
49
68
|
|
69
|
+
# Move the cursor down n lines
|
70
|
+
#
|
71
|
+
# ==== Attributes
|
72
|
+
#
|
73
|
+
# * +n+ - number of lines by which to move the cursor down
|
74
|
+
#
|
50
75
|
def self.cursor_down(n = 1)
|
51
76
|
return '' if n.zero?
|
52
77
|
control(n.to_s, 'B')
|
53
78
|
end
|
54
79
|
|
80
|
+
# Move the cursor forward n columns
|
81
|
+
#
|
82
|
+
# ==== Attributes
|
83
|
+
#
|
84
|
+
# * +n+ - number of columns by which to move the cursor forward
|
85
|
+
#
|
55
86
|
def self.cursor_forward(n = 1)
|
56
87
|
return '' if n.zero?
|
57
88
|
control(n.to_s, 'C')
|
58
89
|
end
|
59
90
|
|
91
|
+
# Move the cursor back n columns
|
92
|
+
#
|
93
|
+
# ==== Attributes
|
94
|
+
#
|
95
|
+
# * +n+ - number of columns by which to move the cursor back
|
96
|
+
#
|
60
97
|
def self.cursor_back(n = 1)
|
61
98
|
return '' if n.zero?
|
62
99
|
control(n.to_s, 'D')
|
63
100
|
end
|
64
101
|
|
102
|
+
# Move the cursor to a specific column
|
103
|
+
#
|
104
|
+
# ==== Attributes
|
105
|
+
#
|
106
|
+
# * +n+ - The column to move to
|
107
|
+
#
|
65
108
|
def self.cursor_horizontal_absolute(n = 1)
|
66
109
|
control(n.to_s, 'G')
|
67
110
|
end
|
68
111
|
|
69
|
-
#
|
70
|
-
|
112
|
+
# Show the cursor
|
113
|
+
#
|
71
114
|
def self.show_cursor
|
72
115
|
control('', "?25h")
|
73
116
|
end
|
74
117
|
|
118
|
+
# Hide the cursor
|
119
|
+
#
|
75
120
|
def self.hide_cursor
|
76
121
|
control('', "?25l")
|
77
122
|
end
|
78
123
|
|
79
|
-
#
|
124
|
+
# Save the cursor position
|
125
|
+
#
|
126
|
+
def self.cursor_save
|
127
|
+
control('', 's')
|
128
|
+
end
|
129
|
+
|
130
|
+
# Restore the saved cursor position
|
131
|
+
#
|
132
|
+
def self.cursor_restore
|
133
|
+
control('', 'u')
|
134
|
+
end
|
80
135
|
|
136
|
+
# Move to the next line
|
137
|
+
#
|
81
138
|
def self.next_line
|
82
139
|
cursor_down + control('1', 'G')
|
83
140
|
end
|
84
141
|
|
142
|
+
# Move to the previous line
|
143
|
+
#
|
85
144
|
def self.previous_line
|
86
145
|
cursor_up + control('1', 'G')
|
87
146
|
end
|
88
147
|
|
148
|
+
# Move to the end of the line
|
149
|
+
#
|
89
150
|
def self.end_of_line
|
90
151
|
control("\033[", 'C')
|
91
152
|
end
|