austb-tty-spinner 0.5.0
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +475 -0
- data/lib/tty-spinner.rb +4 -0
- data/lib/tty/spinner.rb +512 -0
- data/lib/tty/spinner/formats.rb +127 -0
- data/lib/tty/spinner/multi.rb +259 -0
- data/lib/tty/spinner/version.rb +7 -0
- metadata +101 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
module Formats
|
5
|
+
FORMATS = {
|
6
|
+
classic: {
|
7
|
+
interval: 10,
|
8
|
+
frames: %w{| / - \\}
|
9
|
+
},
|
10
|
+
spin: {
|
11
|
+
interval: 10,
|
12
|
+
frames: %w{◴ ◷ ◶ ◵ }
|
13
|
+
},
|
14
|
+
spin_2: {
|
15
|
+
interval: 10,
|
16
|
+
frames: %w{◐ ◓ ◑ ◒ }
|
17
|
+
},
|
18
|
+
spin_3: {
|
19
|
+
interval: 10,
|
20
|
+
frames: %w{◰ ◳ ◲ ◱}
|
21
|
+
},
|
22
|
+
spin_4: {
|
23
|
+
inteval: 10,
|
24
|
+
frames: %w{╫ ╪'}
|
25
|
+
},
|
26
|
+
pulse: {
|
27
|
+
interval: 10,
|
28
|
+
frames: %w{⎺ ⎻ ⎼ ⎽ ⎼ ⎻}
|
29
|
+
},
|
30
|
+
pulse_2: {
|
31
|
+
interval: 15,
|
32
|
+
frames: %w{▁ ▃ ▅ ▆ ▇ █ ▇ ▆ ▅ ▃ }
|
33
|
+
},
|
34
|
+
pulse_3: {
|
35
|
+
interval: 20,
|
36
|
+
frames: '▉▊▋▌▍▎▏▎▍▌▋▊▉'
|
37
|
+
},
|
38
|
+
dots: {
|
39
|
+
interval: 10,
|
40
|
+
frames: [ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" ]
|
41
|
+
},
|
42
|
+
arrow: {
|
43
|
+
interval: 10,
|
44
|
+
frames: %w{← ↖ ↑ ↗ → ↘ ↓ ↙ }
|
45
|
+
},
|
46
|
+
arrow_pulse: {
|
47
|
+
interval: 10,
|
48
|
+
frames: [
|
49
|
+
"▹▹▹▹▹",
|
50
|
+
"▸▹▹▹▹",
|
51
|
+
"▹▸▹▹▹",
|
52
|
+
"▹▹▸▹▹",
|
53
|
+
"▹▹▹▸▹",
|
54
|
+
"▹▹▹▹▸"
|
55
|
+
]
|
56
|
+
},
|
57
|
+
triangle: {
|
58
|
+
interval: 10,
|
59
|
+
frames: %w{◢ ◣ ◤ ◥}
|
60
|
+
},
|
61
|
+
arc: {
|
62
|
+
interval: 10,
|
63
|
+
frames: %w{ ◜ ◠ ◝ ◞ ◡ ◟ }
|
64
|
+
},
|
65
|
+
pipe: {
|
66
|
+
interval: 10,
|
67
|
+
frames: %w{ ┤ ┘ ┴ └ ├ ┌ ┬ ┐ }
|
68
|
+
},
|
69
|
+
bouncing: {
|
70
|
+
interval: 10,
|
71
|
+
frames: [
|
72
|
+
"[ ]",
|
73
|
+
"[ =]",
|
74
|
+
"[ ==]",
|
75
|
+
"[ ===]",
|
76
|
+
"[====]",
|
77
|
+
"[=== ]",
|
78
|
+
"[== ]",
|
79
|
+
"[= ]"
|
80
|
+
]
|
81
|
+
},
|
82
|
+
bouncing_ball: {
|
83
|
+
interval: 10,
|
84
|
+
frames: [
|
85
|
+
"( ● )",
|
86
|
+
"( ● )",
|
87
|
+
"( ● )",
|
88
|
+
"( ● )",
|
89
|
+
"( ●)",
|
90
|
+
"( ● )",
|
91
|
+
"( ● )",
|
92
|
+
"( ● )",
|
93
|
+
"( ● )",
|
94
|
+
"(● )"
|
95
|
+
]
|
96
|
+
},
|
97
|
+
box_bounce: {
|
98
|
+
interval: 10,
|
99
|
+
frames: %w{ ▌ ▀ ▐ ▄ }
|
100
|
+
},
|
101
|
+
box_bounce_2: {
|
102
|
+
interval: 10,
|
103
|
+
frames: %w{ ▖ ▘ ▝ ▗ }
|
104
|
+
},
|
105
|
+
star: {
|
106
|
+
interval: 10,
|
107
|
+
frames: %w{ ✶ ✸ ✹ ✺ ✹ ✷ }
|
108
|
+
},
|
109
|
+
toggle: {
|
110
|
+
interval: 10,
|
111
|
+
frames: %w{ ■ □ ▪ ▫ }
|
112
|
+
},
|
113
|
+
balloon: {
|
114
|
+
interval: 10,
|
115
|
+
frames: %w{ . o O @ * }
|
116
|
+
},
|
117
|
+
balloon_2: {
|
118
|
+
interval: 10,
|
119
|
+
frames: %w{. o O ° O o . }
|
120
|
+
},
|
121
|
+
flip: {
|
122
|
+
interval: 10,
|
123
|
+
frames: '-◡⊙-◠'.freeze
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end # Formats
|
127
|
+
end # TTY
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
require_relative '../spinner'
|
7
|
+
|
8
|
+
module TTY
|
9
|
+
class Spinner
|
10
|
+
# Used for managing multiple terminal spinners
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
class Multi
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def_delegators :@spinners, :each, :empty?, :length
|
19
|
+
|
20
|
+
DEFAULT_INSET = {
|
21
|
+
top: Gem.win_platform? ? '+ ' : "\u250c ",
|
22
|
+
middle: Gem.win_platform? ? '|-- ' : "\u251c\u2500\u2500",
|
23
|
+
bottom: Gem.win_platform? ? '|__ ' : "\u2514\u2500\u2500"
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# Initialize a multispinner
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# spinner = TTY::Spinner::Multi.new
|
30
|
+
#
|
31
|
+
# @param [String] message
|
32
|
+
# the optional message to print in front of the top level spinner
|
33
|
+
#
|
34
|
+
# @param [Hash] options
|
35
|
+
# @option options [Hash] :style
|
36
|
+
# keys :top :middle and :bottom can contain Strings that are used to
|
37
|
+
# indent the spinners. Ignored if message is blank
|
38
|
+
# @option options [Object] :output
|
39
|
+
# the object that responds to print call defaulting to stderr
|
40
|
+
# @option options [Boolean] :hide_cursor
|
41
|
+
# display or hide cursor
|
42
|
+
# @option options [Boolean] :clear
|
43
|
+
# clear ouptut when finished
|
44
|
+
# @option options [Float] :interval
|
45
|
+
# the interval for auto spinning
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def initialize(*args)
|
49
|
+
@options = args.last.is_a?(::Hash) ? args.pop : {}
|
50
|
+
message = args.empty? ? nil : args.pop
|
51
|
+
@inset_opts = @options.delete(:style) { DEFAULT_INSET }
|
52
|
+
@create_spinner_lock = Mutex.new
|
53
|
+
@spinners = []
|
54
|
+
@top_spinner = nil
|
55
|
+
@top_spinner = register(message) unless message.nil?
|
56
|
+
|
57
|
+
@callbacks = {
|
58
|
+
success: [],
|
59
|
+
error: [],
|
60
|
+
done: []
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Register a new spinner
|
65
|
+
#
|
66
|
+
# @param [String] pattern
|
67
|
+
# the pattern used for creating spinner
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def register(pattern, options = {}, &job)
|
71
|
+
spinner = TTY::Spinner.new(pattern, @options.merge(options))
|
72
|
+
|
73
|
+
@create_spinner_lock.synchronize do
|
74
|
+
spinner.add_multispinner(self, @spinners.length)
|
75
|
+
spinner.job(&job) if block_given?
|
76
|
+
observe_events(spinner) if @top_spinner
|
77
|
+
@spinners << spinner
|
78
|
+
if @top_spinner
|
79
|
+
@spinners.each { |sp| sp.redraw_indent if sp.spinning? || sp.done? }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
spinner
|
84
|
+
end
|
85
|
+
|
86
|
+
# Observe all child events to notify top spinner of current state
|
87
|
+
#
|
88
|
+
# @param [TTY::Spinner] spinner
|
89
|
+
# the spinner to listen to for events
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
def observe_events(spinner)
|
93
|
+
spinner.on(:success) { @top_spinner.success if success? }
|
94
|
+
.on(:error) { @top_spinner.error if error? }
|
95
|
+
.on(:done) { @top_spinner.stop if done? && !success? && !error? }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Get the top level spinner if it exists
|
99
|
+
#
|
100
|
+
# @return [TTY::Spinner] the top level spinner
|
101
|
+
#
|
102
|
+
# @api public
|
103
|
+
def top_spinner
|
104
|
+
raise "No top level spinner" if @top_spinner.nil?
|
105
|
+
|
106
|
+
@top_spinner
|
107
|
+
end
|
108
|
+
|
109
|
+
# Auto spin the top level spinner & all child spinners
|
110
|
+
# that have scheduled jobs
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def auto_spin
|
114
|
+
raise "No top level spinner" if @top_spinner.nil?
|
115
|
+
|
116
|
+
@top_spinner.auto_spin
|
117
|
+
jobs = []
|
118
|
+
@spinners.each do |spinner|
|
119
|
+
if spinner.job?
|
120
|
+
spinner.auto_spin
|
121
|
+
jobs << Thread.new { spinner.instance_eval(&spinner.job) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
jobs.each(&:join)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Pause all spinners
|
128
|
+
#
|
129
|
+
# @api public
|
130
|
+
def pause
|
131
|
+
@spinners.dup.each(&:pause)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Resume all spinners
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
def resume
|
138
|
+
@spinners.dup.each(&:resume)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Find relative offset position to which to move the current cursor
|
142
|
+
#
|
143
|
+
# The position is found among the registered spinners given the current
|
144
|
+
# position the spinner is at provided its index
|
145
|
+
#
|
146
|
+
# @param [Integer] index
|
147
|
+
# the position to search from
|
148
|
+
#
|
149
|
+
# @return [Integer]
|
150
|
+
# the current position
|
151
|
+
#
|
152
|
+
# @api public
|
153
|
+
def count_line_offset(index)
|
154
|
+
Array(@spinners[index..-1]).reduce(0) do |acc, spinner|
|
155
|
+
if spinner.spinning? || spinner.done?
|
156
|
+
acc += 1
|
157
|
+
end
|
158
|
+
acc
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Find the number of characters to move into the line
|
163
|
+
# before printing the spinner
|
164
|
+
#
|
165
|
+
# @param [TTY::Spinner] spinner
|
166
|
+
# the spinner for which line inset is calculated
|
167
|
+
#
|
168
|
+
# @return [String]
|
169
|
+
# the inset
|
170
|
+
#
|
171
|
+
# @api public
|
172
|
+
def line_inset(spinner)
|
173
|
+
return '' if @top_spinner.nil?
|
174
|
+
|
175
|
+
case spinner
|
176
|
+
when @top_spinner
|
177
|
+
@inset_opts[:top]
|
178
|
+
when @spinners.last
|
179
|
+
@inset_opts[:bottom]
|
180
|
+
else
|
181
|
+
@inset_opts[:middle]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Check if all spinners are done
|
186
|
+
#
|
187
|
+
# @return [Boolean]
|
188
|
+
#
|
189
|
+
# @api public
|
190
|
+
def done?
|
191
|
+
(@spinners - [@top_spinner]).all?(&:done?)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Check if all spinners succeeded
|
195
|
+
#
|
196
|
+
# @return [Boolean]
|
197
|
+
#
|
198
|
+
# @api public
|
199
|
+
def success?
|
200
|
+
(@spinners - [@top_spinner]).all?(&:success?)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Check if any spinner errored
|
204
|
+
#
|
205
|
+
# @return [Boolean]
|
206
|
+
#
|
207
|
+
# @api public
|
208
|
+
def error?
|
209
|
+
(@spinners - [@top_spinner]).any?(&:error?)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Stop all spinners
|
213
|
+
#
|
214
|
+
# @api public
|
215
|
+
def stop
|
216
|
+
@spinners.dup.each(&:stop)
|
217
|
+
emit :done
|
218
|
+
end
|
219
|
+
|
220
|
+
# Stop all spinners with success status
|
221
|
+
#
|
222
|
+
# @api public
|
223
|
+
def success
|
224
|
+
@top_spinner.success if @top_spinner
|
225
|
+
@spinners.dup.each(&:success)
|
226
|
+
emit :success
|
227
|
+
end
|
228
|
+
|
229
|
+
# Stop all spinners with error status
|
230
|
+
#
|
231
|
+
# @api public
|
232
|
+
def error
|
233
|
+
@top_spinner.error if @top_spinner
|
234
|
+
@spinners.dup.each(&:error)
|
235
|
+
emit :error
|
236
|
+
end
|
237
|
+
|
238
|
+
# Listen on event
|
239
|
+
#
|
240
|
+
# @api public
|
241
|
+
def on(key, &callback)
|
242
|
+
unless @callbacks.key?(key)
|
243
|
+
raise ArgumentError, "The event #{key} does not exist. "\
|
244
|
+
" Use :success, :error, or :done instead"
|
245
|
+
end
|
246
|
+
@callbacks[key] << callback
|
247
|
+
self
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def emit(key, *args)
|
253
|
+
@callbacks[key].each do |block|
|
254
|
+
block.call(*args)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end # MultiSpinner
|
258
|
+
end # Spinner
|
259
|
+
end # TTY
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: austb-tty-spinner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotr Murach
|
8
|
+
- Austin Blatt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-08-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: tty-cursor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.5.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.5.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.5.0
|
35
|
+
- - "<"
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.5.0
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: A terminal spinner for tasks that have non-deterministic time frame.
|
63
|
+
email:
|
64
|
+
- austinblatt@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- lib/tty-spinner.rb
|
72
|
+
- lib/tty/spinner.rb
|
73
|
+
- lib/tty/spinner/formats.rb
|
74
|
+
- lib/tty/spinner/multi.rb
|
75
|
+
- lib/tty/spinner/version.rb
|
76
|
+
homepage: https://github.com/austb/tty-spinner
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.5.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: A terminal spinner for tasks that have non-deterministic time frame.
|
100
|
+
test_files: []
|
101
|
+
has_rdoc:
|