dev-ui 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/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
|
+

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

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

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

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

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

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

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

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