whirly 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ {
2
+ "roman_numerals": {
3
+ "interval": 90,
4
+ "mode": "swing",
5
+ "frames": [
6
+ "Ⅰ",
7
+ "Ⅱ",
8
+ "Ⅲ",
9
+ "Ⅳ",
10
+ "Ⅴ",
11
+ "Ⅵ",
12
+ "Ⅶ",
13
+ "Ⅷ",
14
+ "Ⅸ",
15
+ "Ⅹ"
16
+ ]
17
+ },
18
+ "double_mark": {
19
+ "interval": 120,
20
+ "mode": "random",
21
+ "frames": [
22
+ "⁇",
23
+ "⁈",
24
+ "⁉",
25
+ "‼"
26
+ ]
27
+ },
28
+ "heart_exclamation": {
29
+ "interval": 45,
30
+ "frames": [
31
+ "❢",
32
+ "❣"
33
+ ]
34
+ },
35
+ "pencil": {
36
+ "interval": 200,
37
+ "frames": [
38
+ "✏",
39
+ "✎"
40
+ ]
41
+ },
42
+ "bars": {
43
+ "interval": 80,
44
+ "mode": "swing",
45
+ "frames": [
46
+ "𝍠",
47
+ "𝍡",
48
+ "𝍢",
49
+ "𝍣",
50
+ "𝍤"
51
+ ]
52
+ },
53
+ "dice": {
54
+ "interval": 100,
55
+ "mode": "random",
56
+ "frames": [
57
+ "⚀",
58
+ "⚁",
59
+ "⚂",
60
+ "⚃",
61
+ "⚄",
62
+ "⚅"
63
+ ]
64
+ },
65
+ "hanoi": {
66
+ "interval": 150,
67
+ "mode": "swing",
68
+ "frames": [
69
+ "𝍥",
70
+ "𝍦",
71
+ "𝍧",
72
+ "𝍨"
73
+ ]
74
+ },
75
+ "vertical_bars": {
76
+ "interval": 80,
77
+ "mode": "swing",
78
+ "frames": [
79
+ "𝍩",
80
+ "𝍪",
81
+ "𝍫",
82
+ "𝍬",
83
+ "𝍭"
84
+ ]
85
+ }
86
+ }
@@ -0,0 +1,21 @@
1
+ require_relative "../lib/whirly"
2
+ require "paint"
3
+
4
+ # Demonstrates all available spinners
5
+
6
+ if spinner_pack = $*[0]
7
+ constants = [spinner_pack.upcase]
8
+ else
9
+ constants = Whirly::Spinners.constants
10
+ end
11
+
12
+ constants.each{ |spinner_pack|
13
+ puts
14
+ puts Paint[spinner_pack, :underline]
15
+ puts
16
+ Whirly::Spinners.const_get(spinner_pack).keys.sort.each{ |spinner_name|
17
+ Whirly.start(spinner: spinner_name, status: spinner_name){
18
+ sleep 1.5
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ require_relative "../lib/whirly"
2
+ require "paint"
3
+
4
+ system "clear"
5
+
6
+ Whirly::Spinners::WHIRLY.keys.sort.each{ |spinner_name|
7
+ Whirly.start(spinner: spinner_name, status: spinner_name, append_newline: false, ansi_escape_mode: "line", remove_after_stop: true){
8
+ sleep 1.5
9
+ }
10
+ }
11
+
12
+ system "exit"
@@ -1,6 +1,8 @@
1
- require_relative 'lib/whirly'
1
+ require_relative '../lib/whirly'
2
2
  require 'paint'
3
3
 
4
+ # Lightning talk at EuRuKo 2016
5
+
4
6
  # # # Whirly
5
7
 
6
8
  print "\033c"
@@ -22,16 +24,6 @@ puts
22
24
  puts "Done"
23
25
  sleep 5
24
26
 
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
27
  # # # Earth
36
28
 
37
29
  print "\033c"
@@ -48,14 +40,6 @@ puts
48
40
  puts "Done"
49
41
  sleep 5
50
42
 
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
43
  # # # Pong Game
60
44
 
61
45
  print "\033c"
@@ -65,22 +49,12 @@ Whirly.start spinner: "pong", use_color: false, status: "Two computers in a game
65
49
  sleep 9
66
50
  end
67
51
 
68
-
69
52
  puts
70
53
  puts
71
54
  puts
72
55
  puts "Done"
73
56
  sleep 5
74
57
 
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
58
  # # # Ticking Clock
85
59
 
86
60
  print "\033c"
@@ -95,23 +69,11 @@ puts
95
69
  puts
96
70
  puts "Time is over"
97
71
 
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
72
  # # # URL
109
73
 
110
-
111
74
  print "\033c"
112
75
  puts Paint["Get WHIRLY", :bold]
113
76
 
114
- Whirly.start status: 'https://github.com/janlelis/whirly' do
77
+ Whirly.start spinner: "whirly", status: "https://github.com/janlelis/whirly" do
115
78
  sleep 60
116
79
  end
117
-
@@ -0,0 +1,20 @@
1
+ require_relative "../lib/whirly"
2
+ require "paint"
3
+
4
+ # Demonstrate the look of using multiple spinners
5
+
6
+ Whirly.configure(spinner: "dots", stop: "✔")
7
+
8
+ Whirly.start status: "Processing" do
9
+ sleep 2
10
+ end
11
+
12
+ Whirly.start status: "More processing" do
13
+ sleep 2
14
+ end
15
+
16
+ Whirly.start status: "Even more processing" do
17
+ sleep 2
18
+ end
19
+
20
+ puts "Done"
@@ -0,0 +1,6 @@
1
+ require_relative "../lib/whirly"
2
+ require "paint"
3
+
4
+ # Call a single spinner from the command-line
5
+
6
+ Whirly.start(spinner: $*[0], status: $*[2] || $*[0], use_color: false){ sleep(($*[1] || 10).to_i) }
@@ -0,0 +1,7 @@
1
+ require_relative "../lib/whirly"
2
+
3
+ Whirly.start status: "Initial status, passed when starting Whirly"
4
+ sleep 3
5
+ Whirly.status = "Status update"
6
+ sleep 3
7
+ Whirly.stop
data/lib/whirly.rb CHANGED
@@ -1,78 +1,163 @@
1
1
  require_relative "whirly/version"
2
2
  require_relative "whirly/spinners"
3
3
 
4
- require "paint"
4
+ require "unicode/display_width"
5
5
 
6
- # TODO configure extra-line below
7
- # TODO clear previous frame
6
+ begin
7
+ require "paint"
8
+ rescue LoadError
9
+ end
8
10
 
9
11
  module Whirly
12
+ @configured = false
13
+
10
14
  CLI_COMMANDS = {
11
15
  hide_cursor: "\x1b[?25l",
12
16
  show_cursor: "\x1b[?25h",
13
- }
17
+ }.freeze
18
+
19
+ DEFAULT_OPTIONS = {
20
+ ambiguous_character_width: 1,
21
+ ansi_escape_mode: "restore",
22
+ append_newline: true,
23
+ color: !!defined?(Paint),
24
+ color_change_rate: 30,
25
+ hide_cursor: true,
26
+ non_tty: false,
27
+ position: "normal",
28
+ remove_after_stop: false,
29
+ spinner: "whirly",
30
+ spinner_packs: [:whirly, :cli],
31
+ status: nil,
32
+ stream: $stdout,
33
+ }.freeze
34
+
35
+ SOFT_DEFAULT_OPTIONS = {
36
+ interval: 100,
37
+ mode: "linear",
38
+ stop: nil,
39
+ }.freeze
14
40
 
15
41
  class << self
16
42
  attr_accessor :status
17
- end
43
+ attr_reader :options
18
44
 
19
- def self.enabled?
20
- @enabled
21
- end
22
-
23
- def self.paused?
24
- @paused
45
+ def enabled?
46
+ !!(defined?(@enabled) && @enabled)
47
+ end
48
+
49
+ def configured?
50
+ !!(@configured)
51
+ end
25
52
  end
26
53
 
27
- def self.start(stream: $stdout,
28
- interval: nil,
29
- spinner: "whirly",
30
- use_color: defined?(Paint),
31
- color_change_rate: 30,
32
- status: nil,
33
- hide_cursor: true,
34
- non_tty: false)
35
- # only actviate if we are on a real terminal (or forced)
36
- return false unless stream.tty? || non_tty
37
-
38
- # ensure cursor is visible after exit
39
- at_exit{ @stream.print CLI_COMMANDS[:show_cursor] } if !defined?(@enabled) && hide_cursor
40
-
41
- # only activate once
42
- return false if @enabled
43
-
44
- # save options and preprocess
45
- @enabled = true
46
- @paused = false
47
- @stream = stream
48
- @status = status
49
- if spinner.is_a? Hash
50
- @spinner = spinner
54
+ # set spinner directly or lookup
55
+ def self.configure_spinner(spinner_option)
56
+ case spinner_option
57
+ when Hash
58
+ spinner = spinner_option.dup
59
+ when Enumerable
60
+ spinner = { "frames" => spinner_option.dup }
61
+ when Proc
62
+ spinner = { "proc" => spinner_option.dup }
51
63
  else
52
- @spinner = SPINNERS[spinner.to_s]
64
+ spinner = nil
65
+ catch(:found){
66
+ @options[:spinner_packs].each{ |spinner_pack|
67
+ spinners = Whirly::Spinners.const_get(spinner_pack.to_s.upcase)
68
+ if spinners[spinner_option]
69
+ spinner = spinners[spinner_option].dup
70
+ throw(:found)
71
+ end
72
+ }
73
+ }
53
74
  end
54
- raise(ArgumentError, "Whirly: Invalid spinner given") if !@spinner || (!@spinner["frames"] && !@spinner["proc"])
55
- @hide_cursor = hide_cursor
56
- @interval = (interval || @spinner["interval"] || 100) * 0.001
57
- @frames = @spinner["frames"] && @spinner["frames"].cycle
58
- @proc = @spinner["proc"]
59
75
 
60
- # init color
61
- if use_color
62
- if defined?(Paint)
63
- @color = "%.6x" % rand(16777216)
64
- @color_directions = (0..2).map{ |e| rand(3) - 1 }
65
- @color_change_rate = color_change_rate
76
+ # validate spinner
77
+ if !spinner || (!spinner["frames"] && !spinner["proc"])
78
+ raise(ArgumentError, "Whirly: Invalid spinner given")
79
+ end
80
+
81
+ spinner
82
+ end
83
+
84
+ # frames can be generated from enumerables or procs
85
+ def self.configure_frames(spinner)
86
+ if spinner["frames"]
87
+ case spinner["mode"]
88
+ when "swing"
89
+ frames = (spinner["frames"].to_a + spinner["frames"].to_a[1..-2].reverse).cycle
90
+ when "random"
91
+ frame_pool = spinner["frames"].to_a
92
+ frames = ->(){ frame_pool.sample }
93
+ when "reverse"
94
+ frames = spinner["frames"].to_a.reverse.cycle
66
95
  else
67
- warn "Whirly warning: Using colors requires the paint gem"
96
+ frames = spinner["frames"].cycle
97
+ end
98
+ elsif spinner["proc"]
99
+ frames = spinner["proc"].dup
100
+ else
101
+ raise(ArgumentError, "Whirly: Invalid spinner given")
102
+ end
103
+
104
+ if frames.is_a? Proc
105
+ class << frames
106
+ alias next call
68
107
  end
69
108
  end
70
109
 
110
+ frames
111
+ end
112
+
113
+ # save options and preprocess, set defaults if value is still unknown
114
+ def self.configure(**options)
115
+ if !defined?(@configured) || !@configured || !defined?(@options) || !@options
116
+ @options = DEFAULT_OPTIONS.dup
117
+ @configured = true
118
+ end
119
+
120
+ @options.merge!(options)
121
+
122
+ spinner = configure_spinner(@options[:spinner])
123
+ spinner_overwrites = {}
124
+ spinner_overwrites["mode"] = @options[:mode] if @options.key?(:mode)
125
+ @frames = configure_frames(spinner.merge(spinner_overwrites))
126
+
127
+ @interval = (@options[:interval] || spinner["interval"] || SOFT_DEFAULT_OPTIONS[:interval]) * 0.001
128
+ @stop = @options[:stop] || spinner["stop"]
129
+ @status = @options[:status]
130
+ end
131
+
132
+ def self.start(**options)
133
+ # optionally overwrite configuration on start
134
+ configure(**options)
135
+
136
+ # ensure cursor is visible after exit the program (only register for the very first time)
137
+ if (!defined?(@at_exit_handler_registered) || !@at_exit_handler_registered) && @options[:hide_cursor]
138
+ @at_exit_handler_registered = true
139
+ stream = @options[:stream]
140
+ at_exit{ stream.print CLI_COMMANDS[:show_cursor] }
141
+ end
142
+
143
+ # only enable once
144
+ return false if defined?(@enabled) && @enabled
145
+
146
+ # set status to enabled
147
+ @enabled = true
148
+
149
+ # only do something if we are on a real terminal (or forced)
150
+ return false unless @options[:stream].tty? || @options[:non_tty]
151
+
152
+ # init color
153
+ initialize_color if @options[:color]
154
+
71
155
  # hide cursor
72
- @stream.print CLI_COMMANDS[:hide_cursor] if @hide_cursor
156
+ @options[:stream].print CLI_COMMANDS[:hide_cursor] if @options[:hide_cursor]
73
157
 
74
158
  # start spinner loop
75
159
  @thread = Thread.new do
160
+ @current_frame = nil
76
161
  while true # it's just a spinner, no exact timing here
77
162
  next_color if @color
78
163
  render
@@ -80,7 +165,7 @@ module Whirly
80
165
  end
81
166
  end
82
167
 
83
- # idiomatic block syntax
168
+ # idiomatic block syntax support
84
169
  if block_given?
85
170
  yield
86
171
  Whirly.stop
@@ -89,50 +174,76 @@ module Whirly
89
174
  true
90
175
  end
91
176
 
92
- def self.stop(delete = false)
177
+ def self.stop(stop_frame = nil)
93
178
  return false unless @enabled
94
- @thread.terminate
179
+ @thread.terminate if @thread
180
+ render(stop_frame || @stop) if stop_frame || @stop
181
+ unrender if @options[:remove_after_stop]
182
+ @options[:stream].puts if @options[:append_newline]
183
+ @options[:stream].print CLI_COMMANDS[:show_cursor] if @options[:hide_cursor]
95
184
  @enabled = false
96
- @stream.print CLI_COMMANDS[:show_cursor] if @hide_cursor
97
- print "TODO" if delete
98
-
185
+
99
186
  true
100
187
  end
101
188
 
102
- def self.pause
103
- # unrender
104
- @paused = true
105
- @stream.print CLI_COMMANDS[:show_cursor] if @hide_cursor
106
- if block_given?
107
- yield
108
- continue
109
- end
189
+ def self.reset
190
+ at_exit_handler_registered = defined?(@at_exit_handler_registered) && @at_exit_handler_registered
191
+ instance_variables.each{ |iv| remove_instance_variable(iv) }
192
+ @at_exit_handler_registered = at_exit_handler_registered
193
+ @configured = false
110
194
  end
111
195
 
112
- def self.continue
113
- @stream.print CLI_COMMANDS[:hide_cursor] if @hide_cursor
114
- @paused = false
115
- end
196
+ # - - -
116
197
 
117
198
  def self.unrender
118
199
  return unless @current_frame
119
- current_frame_size = @current_frame.size
120
- @stream.print "\n\e[s#{' ' * current_frame_size}\e[u\e[1A"
200
+ case @options[:ansi_escape_mode]
201
+ when "restore"
202
+ @options[:stream].print(render_prefix + (
203
+ ' ' * (Unicode::DisplayWidth.of(@current_frame, @options[:ambiguous_character_width]) + 1)
204
+ ) + render_suffix)
205
+ when "line"
206
+ @options[:stream].print "\e[1K"
207
+ end
121
208
  end
122
209
 
123
- def self.render
124
- return if @paused
210
+ def self.render(next_frame = nil)
125
211
  unrender
126
- @current_frame = @proc ? @proc.call : @frames.next
127
- @current_frame = Paint[@current_frame, @color] if @color
212
+
213
+ @current_frame = next_frame || @frames.next
214
+ @current_frame = Paint[@current_frame, @color] if @options[:color]
128
215
  @current_frame += " #{@status}" if @status
129
- # @stream.print "\e[s#{@current_frame}\e[u"
130
- @stream.print "\n\e[s#{@current_frame}\e[u\e[1A"
216
+
217
+ @options[:stream].print(render_prefix + @current_frame.to_s + render_suffix)
218
+ end
219
+
220
+ def self.render_prefix
221
+ res = ""
222
+ res << "\n" if @options[:position] == "below"
223
+ res << "\e[s" if @options[:ansi_escape_mode] == "restore"
224
+ res << "\e[G" if @options[:ansi_escape_mode] == "line"
225
+ res
226
+ end
227
+
228
+ def self.render_suffix
229
+ res = ""
230
+ res << "\e[u" if @options[:ansi_escape_mode] == "restore"
231
+ res << "\e[1A" if @options[:position] == "below"
232
+ res
233
+ end
234
+
235
+ def self.initialize_color
236
+ if !defined?(Paint)
237
+ warn "Whirly warning: Using colors requires the paint gem"
238
+ else
239
+ @color = "%.6x" % rand(16777216)
240
+ @color_directions = (0..2).map{ |e| rand(3) - 1 }
241
+ end
131
242
  end
132
243
 
133
244
  def self.next_color
134
245
  @color = @color.scan(/../).map.with_index{ |c, i|
135
- color_change = rand(@color_change_rate) * @color_directions[i]
246
+ color_change = rand(@options[:color_change_rate]) * @color_directions[i]
136
247
  nc = c.to_i(16) + color_change
137
248
  if nc <= 0
138
249
  nc = 0