planter-cli 3.0.1 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.gitmodules +3 -0
  4. data/.rubocop.yml +1 -2
  5. data/CHANGELOG.md +24 -0
  6. data/README.md +85 -4
  7. data/bin/plant +1 -1
  8. data/docker/Dockerfile +2 -4
  9. data/docker/Dockerfile-2.6 +4 -5
  10. data/docker/Dockerfile-2.7 +4 -5
  11. data/docker/Dockerfile-3.0 +4 -4
  12. data/docker/Dockerfile-3.3 +12 -0
  13. data/docker/bash_profile +2 -1
  14. data/docker/sources.list +11 -0
  15. data/lib/planter/array.rb +56 -1
  16. data/lib/planter/filelist.rb +5 -4
  17. data/lib/planter/hash.rb +24 -0
  18. data/lib/planter/plant.rb +6 -4
  19. data/lib/planter/prompt.rb +56 -16
  20. data/lib/planter/string.rb +143 -5
  21. data/lib/planter/tag.rb +39 -2
  22. data/lib/planter/version.rb +1 -1
  23. data/lib/planter.rb +30 -17
  24. data/lib/tty-spinner/.editorconfig +9 -0
  25. data/lib/tty-spinner/.github/FUNDING.yml +1 -0
  26. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/BUG_REPORT.md +31 -0
  27. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +23 -0
  28. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/config.yml +5 -0
  29. data/lib/tty-spinner/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  30. data/lib/tty-spinner/.github/workflows/ci.yml +59 -0
  31. data/lib/tty-spinner/.gitignore +14 -0
  32. data/lib/tty-spinner/.rspec +2 -0
  33. data/lib/tty-spinner/.rubocop.yml +78 -0
  34. data/lib/tty-spinner/CHANGELOG.md +151 -0
  35. data/lib/tty-spinner/CODE_OF_CONDUCT.md +132 -0
  36. data/lib/tty-spinner/Gemfile +17 -0
  37. data/lib/tty-spinner/LICENSE.txt +22 -0
  38. data/lib/tty-spinner/README.md +581 -0
  39. data/lib/tty-spinner/Rakefile +10 -0
  40. data/lib/tty-spinner/appveyor.yml +33 -0
  41. data/lib/tty-spinner/bin/console +14 -0
  42. data/lib/tty-spinner/bin/setup +8 -0
  43. data/lib/tty-spinner/demo.gif +0 -0
  44. data/lib/tty-spinner/examples/auto_spin.rb +10 -0
  45. data/lib/tty-spinner/examples/basic.rb +10 -0
  46. data/lib/tty-spinner/examples/clear.rb +11 -0
  47. data/lib/tty-spinner/examples/color.rb +14 -0
  48. data/lib/tty-spinner/examples/error.rb +11 -0
  49. data/lib/tty-spinner/examples/formats.rb +13 -0
  50. data/lib/tty-spinner/examples/hide_cursor.rb +14 -0
  51. data/lib/tty-spinner/examples/log.rb +13 -0
  52. data/lib/tty-spinner/examples/multi/basic.rb +15 -0
  53. data/lib/tty-spinner/examples/multi/basic_top_level.rb +15 -0
  54. data/lib/tty-spinner/examples/multi/custom_style.rb +28 -0
  55. data/lib/tty-spinner/examples/multi/files.rb +16 -0
  56. data/lib/tty-spinner/examples/multi/jobs.rb +11 -0
  57. data/lib/tty-spinner/examples/multi/multi.rb +19 -0
  58. data/lib/tty-spinner/examples/multi/multi_top_level.rb +20 -0
  59. data/lib/tty-spinner/examples/multi/pause.rb +28 -0
  60. data/lib/tty-spinner/examples/multi/threaded.rb +30 -0
  61. data/lib/tty-spinner/examples/pause.rb +24 -0
  62. data/lib/tty-spinner/examples/run.rb +20 -0
  63. data/lib/tty-spinner/examples/success.rb +11 -0
  64. data/lib/tty-spinner/examples/threaded.rb +13 -0
  65. data/lib/tty-spinner/examples/update.rb +13 -0
  66. data/lib/tty-spinner/lib/tty/spinner/formats.rb +274 -0
  67. data/lib/tty-spinner/lib/tty/spinner/multi.rb +352 -0
  68. data/lib/tty-spinner/lib/tty/spinner/version.rb +7 -0
  69. data/lib/tty-spinner/lib/tty/spinner.rb +604 -0
  70. data/lib/tty-spinner/lib/tty-spinner.rb +2 -0
  71. data/lib/tty-spinner/spec/spec_helper.rb +52 -0
  72. data/lib/tty-spinner/spec/unit/auto_spin_spec.rb +25 -0
  73. data/lib/tty-spinner/spec/unit/clear_spec.rb +16 -0
  74. data/lib/tty-spinner/spec/unit/error_spec.rb +53 -0
  75. data/lib/tty-spinner/spec/unit/events_spec.rb +35 -0
  76. data/lib/tty-spinner/spec/unit/formats_spec.rb +9 -0
  77. data/lib/tty-spinner/spec/unit/frames_spec.rb +31 -0
  78. data/lib/tty-spinner/spec/unit/hide_cursor_spec.rb +51 -0
  79. data/lib/tty-spinner/spec/unit/job_spec.rb +12 -0
  80. data/lib/tty-spinner/spec/unit/join_spec.rb +10 -0
  81. data/lib/tty-spinner/spec/unit/log_spec.rb +60 -0
  82. data/lib/tty-spinner/spec/unit/multi/auto_spin_spec.rb +32 -0
  83. data/lib/tty-spinner/spec/unit/multi/error_spec.rb +107 -0
  84. data/lib/tty-spinner/spec/unit/multi/line_inset_spec.rb +57 -0
  85. data/lib/tty-spinner/spec/unit/multi/on_spec.rb +11 -0
  86. data/lib/tty-spinner/spec/unit/multi/register_spec.rb +46 -0
  87. data/lib/tty-spinner/spec/unit/multi/spin_spec.rb +101 -0
  88. data/lib/tty-spinner/spec/unit/multi/stop_spec.rb +95 -0
  89. data/lib/tty-spinner/spec/unit/multi/success_spec.rb +108 -0
  90. data/lib/tty-spinner/spec/unit/new_spec.rb +25 -0
  91. data/lib/tty-spinner/spec/unit/pause_spec.rb +43 -0
  92. data/lib/tty-spinner/spec/unit/reset_spec.rb +19 -0
  93. data/lib/tty-spinner/spec/unit/run_spec.rb +30 -0
  94. data/lib/tty-spinner/spec/unit/spin_spec.rb +117 -0
  95. data/lib/tty-spinner/spec/unit/stop_spec.rb +88 -0
  96. data/lib/tty-spinner/spec/unit/success_spec.rb +53 -0
  97. data/lib/tty-spinner/spec/unit/tty_spec.rb +8 -0
  98. data/lib/tty-spinner/spec/unit/update_spec.rb +85 -0
  99. data/lib/tty-spinner/tasks/console.rake +11 -0
  100. data/lib/tty-spinner/tasks/coverage.rake +11 -0
  101. data/lib/tty-spinner/tasks/spec.rake +29 -0
  102. data/lib/tty-spinner/tty-spinner.gemspec +36 -0
  103. data/scripts/runtests.sh +1 -1
  104. data/spec/cli_spec.rb +27 -0
  105. data/spec/planter/string_spec.rb +31 -4
  106. data/spec/spec_helper.rb +26 -0
  107. data/spec/templates/test/_planter.yml +3 -6
  108. data/src/_README.md +85 -4
  109. metadata +86 -2
@@ -0,0 +1,604 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require "tty-cursor"
5
+
6
+ require_relative "spinner/version"
7
+ require_relative "spinner/formats"
8
+
9
+ module TTY
10
+ # Used for creating terminal spinner
11
+ #
12
+ # @api public
13
+ class Spinner
14
+ include Formats
15
+ include MonitorMixin
16
+
17
+ # @raised when attempting to join dead thread
18
+ NotSpinningError = Class.new(StandardError)
19
+
20
+ ECMA_CSI = "\x1b["
21
+
22
+ MATCHER = /:spinner/
23
+ TICK = "✔"
24
+ CROSS = "✖"
25
+
26
+ CURSOR_LOCK = Monitor.new
27
+
28
+ # The object that responds to print call defaulting to stderr
29
+ #
30
+ # @api public
31
+ attr_reader :output
32
+
33
+ # The current format type
34
+ #
35
+ # @return [String]
36
+ #
37
+ # @api public
38
+ attr_reader :format
39
+
40
+ # Whether to show or hide cursor
41
+ #
42
+ # @return [Boolean]
43
+ #
44
+ # @api public
45
+ attr_reader :hide_cursor
46
+
47
+ # The message to print before the spinner
48
+ #
49
+ # @return [String]
50
+ # the current message
51
+ #
52
+ # @api public
53
+ attr_reader :message
54
+
55
+ # Tokens for the message
56
+ #
57
+ # @return [Hash[Symbol, Object]]
58
+ # the current tokens
59
+ #
60
+ # @api public
61
+ attr_reader :tokens
62
+
63
+ # The amount of time between frames in auto spinning
64
+ #
65
+ # @api public
66
+ attr_reader :interval
67
+
68
+ # The current row inside the multi spinner
69
+ #
70
+ # @api public
71
+ attr_reader :row
72
+
73
+ # Initialize a spinner
74
+ #
75
+ # @example
76
+ # spinner = TTY::Spinner.new
77
+ #
78
+ # @param [String] message
79
+ # the message to print in front of the spinner
80
+ #
81
+ # @param [Hash] options
82
+ # @option options [String] :format
83
+ # the spinner format type defaulting to :spin_1
84
+ # @option options [Object] :output
85
+ # the object that responds to print call defaulting to stderr
86
+ # @option options [Boolean] :hide_cursor
87
+ # display or hide cursor
88
+ # @option options [Boolean] :clear
89
+ # clear ouptut when finished
90
+ # @option options [Float] :interval
91
+ # the interval for auto spinning
92
+ #
93
+ # @api public
94
+ def initialize(*args)
95
+ super()
96
+ options = args.last.is_a?(::Hash) ? args.pop : {}
97
+ @message = args.empty? ? ":spinner" : args.pop
98
+ @tokens = {}
99
+
100
+ @format = options.fetch(:format) { :classic }
101
+ @output = options.fetch(:output) { $stderr }
102
+ @hide_cursor = options.fetch(:hide_cursor) { false }
103
+ @frames = options.fetch(:frames) do
104
+ fetch_format(@format.to_sym, :frames)
105
+ end
106
+ @clear = options.fetch(:clear) { false }
107
+ @success_mark = options.fetch(:success_mark) { TICK }
108
+ @error_mark = options.fetch(:error_mark) { CROSS }
109
+ @interval = options.fetch(:interval) do
110
+ fetch_format(@format.to_sym, :interval)
111
+ end
112
+ @row = options[:row]
113
+
114
+ @callbacks = Hash.new { |h, k| h[k] = [] }
115
+ @length = @frames.length
116
+ @thread = nil
117
+ @job = nil
118
+ @multispinner = nil
119
+ reset
120
+ end
121
+
122
+ # Reset the spinner to initial frame
123
+ #
124
+ # @api public
125
+ def reset
126
+ synchronize do
127
+ @current = 0
128
+ @done = false
129
+ @state = :stopped
130
+ @succeeded = false
131
+ @first_run = true
132
+ end
133
+ end
134
+
135
+ # Notifies the TTY::Spinner that it is running under a multispinner
136
+ #
137
+ # @param [TTY::Spinner::Multi] the multispinner that it is running under
138
+ #
139
+ # @api private
140
+ def attach_to(multispinner)
141
+ @multispinner = multispinner
142
+ end
143
+
144
+ # Whether the spinner has completed spinning
145
+ #
146
+ # @return [Boolean] whether or not the spinner has finished
147
+ #
148
+ # @api public
149
+ def done?
150
+ @done
151
+ end
152
+
153
+ # Whether the spinner is spinning
154
+ #
155
+ # @return [Boolean] whether or not the spinner is spinning
156
+ #
157
+ # @api public
158
+ def spinning?
159
+ @state == :spinning
160
+ end
161
+
162
+ # Whether the spinner is in the success state.
163
+ # When true the spinner is marked with a success mark.
164
+ #
165
+ # @return [Boolean] whether or not the spinner succeeded
166
+ #
167
+ # @api public
168
+ def success?
169
+ @succeeded == :success
170
+ end
171
+
172
+ # Whether the spinner is in the error state. This is only true
173
+ # temporarily while it is being marked with a failure mark.
174
+ #
175
+ # @return [Boolean] whether or not the spinner is erroring
176
+ #
177
+ # @api public
178
+ def error?
179
+ @succeeded == :error
180
+ end
181
+
182
+ # Register callback
183
+ #
184
+ # @param [Symbol] name
185
+ # the name for the event to listen for, e.i. :complete
186
+ #
187
+ # @return [self]
188
+ #
189
+ # @api public
190
+ def on(name, &block)
191
+ synchronize do
192
+ @callbacks[name] << block
193
+ end
194
+ self
195
+ end
196
+
197
+ # Start timer and unlock spinner
198
+ #
199
+ # @api public
200
+ def start
201
+ @started_at = Time.now
202
+ @done = false
203
+ reset
204
+ end
205
+
206
+ # Add job to this spinner
207
+ #
208
+ # @api public
209
+ def job(&work)
210
+ synchronize do
211
+ if block_given?
212
+ @job = work
213
+ else
214
+ @job
215
+ end
216
+ end
217
+ end
218
+
219
+ # Execute this spinner job
220
+ #
221
+ # @yield [TTY::Spinner]
222
+ #
223
+ # @api public
224
+ def execute_job
225
+ job.call(self) if job?
226
+ end
227
+
228
+ # Check if this spinner has a scheduled job
229
+ #
230
+ # @return [Boolean]
231
+ #
232
+ # @api public
233
+ def job?
234
+ !@job.nil?
235
+ end
236
+
237
+ # Start automatic spinning animation
238
+ #
239
+ # @api public
240
+ def auto_spin
241
+ CURSOR_LOCK.synchronize do
242
+ start
243
+ sleep_time = 1.0 / @interval
244
+
245
+ spin
246
+ @thread = Thread.new do
247
+ sleep(sleep_time)
248
+ while @started_at
249
+ if Thread.current["pause"]
250
+ Thread.stop
251
+ Thread.current["pause"] = false
252
+ end
253
+ spin
254
+ sleep(sleep_time)
255
+ end
256
+ end
257
+ end
258
+ ensure
259
+ write(TTY::Cursor.show, false) if @hide_cursor
260
+ end
261
+
262
+ # Checked if current spinner is paused
263
+ #
264
+ # @return [Boolean]
265
+ #
266
+ # @api public
267
+ def paused?
268
+ !!(@thread && @thread["pause"])
269
+ end
270
+
271
+ # Pause spinner automatic animation
272
+ #
273
+ # @param [String] mark
274
+ # the custom mark to replace the spinner
275
+ #
276
+ # @api public
277
+ def pause(mark: nil)
278
+ return if paused? || done?
279
+
280
+ synchronize do
281
+ data = message.gsub(MATCHER, mark || @frames[@current])
282
+ data = replace_tokens(data)
283
+ write(data, true)
284
+ @thread["pause"] = true if @thread
285
+ end
286
+ end
287
+
288
+ # Resume spinner automatic animation
289
+ #
290
+ # @api public
291
+ def resume
292
+ return unless paused?
293
+
294
+ @thread.wakeup if @thread
295
+ end
296
+
297
+ # Run spinner while executing job
298
+ #
299
+ # @param [String] stop_message
300
+ # the message displayed when block is finished
301
+ #
302
+ # @yield automatically animate and finish spinner
303
+ #
304
+ # @example
305
+ # spinner.run("Migrated DB") { ... }
306
+ #
307
+ # @api public
308
+ def run(stop_message = "", &block)
309
+ job(&block)
310
+ auto_spin
311
+
312
+ @work = Thread.new { execute_job }
313
+ @work.join
314
+ ensure
315
+ stop(stop_message)
316
+ end
317
+
318
+ # Duration of the spinning animation
319
+ #
320
+ # @return [Numeric]
321
+ #
322
+ # @api public
323
+ def duration
324
+ @started_at ? Time.now - @started_at : nil
325
+ end
326
+
327
+ # Join running spinner
328
+ #
329
+ # @param [Float] timeout
330
+ # the timeout for join
331
+ #
332
+ # @api public
333
+ def join(timeout = nil)
334
+ unless @thread
335
+ raise(NotSpinningError, "Cannot join spinner that is not running")
336
+ end
337
+
338
+ timeout ? @thread.join(timeout) : @thread.join
339
+ end
340
+
341
+ # Kill running spinner
342
+ #
343
+ # @api public
344
+ def kill
345
+ synchronize do
346
+ @thread.kill if @thread
347
+ end
348
+ end
349
+
350
+ # Perform a spin
351
+ #
352
+ # @return [String]
353
+ # the printed data
354
+ #
355
+ # @api public
356
+ def spin
357
+ return if done?
358
+
359
+ synchronize do
360
+ emit(:spin)
361
+ render
362
+ @current = (@current + 1) % @length
363
+ @state = :spinning
364
+ end
365
+ end
366
+
367
+ # Render spinner to the output
368
+ #
369
+ # @api private
370
+ def render
371
+ return if done?
372
+
373
+ write(TTY::Cursor.hide) if @hide_cursor && !spinning?
374
+
375
+ data = message.gsub(MATCHER, @frames[@current])
376
+ data = replace_tokens(data)
377
+ write(data, true)
378
+ end
379
+
380
+ # Redraw the indent for this spinner, if it exists
381
+ #
382
+ # @api private
383
+ def redraw_indent
384
+ write(TTY::Cursor.hide) if @hide_cursor && !spinning?
385
+
386
+ write("", false)
387
+ end
388
+
389
+ # Finish spining
390
+ #
391
+ # @param [String] stop_message
392
+ # the stop message to print
393
+ # @param [String] mark
394
+ # the custom mark to replace the spinner
395
+ #
396
+ # @api public
397
+ def stop(stop_message = "", mark: nil)
398
+ mon_enter
399
+ return if done?
400
+
401
+ clear_line
402
+ return if @clear
403
+
404
+ data = message.gsub(MATCHER, mark || next_char)
405
+ data = replace_tokens(data)
406
+ data << (" " + stop_message) unless stop_message.empty?
407
+
408
+ write(data, false)
409
+ write("\n", false) unless @clear || @multispinner
410
+ ensure
411
+ @state = :stopped
412
+ @done = true
413
+ @started_at = nil
414
+
415
+ write(TTY::Cursor.show, false) if @hide_cursor
416
+
417
+ emit(:done)
418
+ kill
419
+ mon_exit
420
+ end
421
+
422
+ # Retrieve next character
423
+ #
424
+ # @return [String]
425
+ #
426
+ # @api private
427
+ def next_char
428
+ if success?
429
+ @success_mark
430
+ elsif error?
431
+ @error_mark
432
+ else
433
+ @frames[@current - 1]
434
+ end
435
+ end
436
+
437
+ # Finish spinning and set state to :success
438
+ #
439
+ # @param [String] stop_message
440
+ # the message to display on success
441
+ # @param [String] mark
442
+ # the custom mark to replace the spinner
443
+ #
444
+ # @api public
445
+ def success(stop_message = "", mark: nil)
446
+ return if done?
447
+
448
+ synchronize do
449
+ @succeeded = :success
450
+ stop(stop_message, mark: mark)
451
+ emit(:success)
452
+ end
453
+ end
454
+
455
+ # Finish spinning and set state to :error
456
+ #
457
+ # @param [String] stop_message
458
+ # the message to display on error
459
+ # @param [String] mark
460
+ # the custom mark to replace the spinner
461
+ #
462
+ # @api public
463
+ def error(stop_message = "", mark: nil)
464
+ return if done?
465
+
466
+ synchronize do
467
+ @succeeded = :error
468
+ stop(stop_message, mark: mark)
469
+ emit(:error)
470
+ end
471
+ end
472
+
473
+ # Clear current line
474
+ #
475
+ # @api public
476
+ def clear_line
477
+ write(ECMA_CSI + "0m" + TTY::Cursor.clear_line)
478
+ end
479
+
480
+ # Update string formatting tokens
481
+ #
482
+ # @param [Hash[Symbol]] tokens
483
+ # the tokens used in formatting string
484
+ #
485
+ # @api public
486
+ def update(tokens)
487
+ synchronize do
488
+ clear_line if spinning?
489
+ @tokens.merge!(tokens)
490
+ end
491
+ end
492
+
493
+ # Log text above the current spinner
494
+ #
495
+ # @param [String] text
496
+ # the message to log out
497
+ #
498
+ # @api public
499
+ def log(text)
500
+ synchronize do
501
+ cleared_text = text.to_s.lines.map do |line|
502
+ TTY::Cursor.clear_line + line
503
+ end.join
504
+
505
+ write("#{cleared_text}#{"\n" unless cleared_text.end_with?("\n")}", false)
506
+ render
507
+ end
508
+ end
509
+
510
+ # Check if IO is attached to a terminal
511
+ #
512
+ # return [Boolean]
513
+ #
514
+ # @api public
515
+ def tty?
516
+ output.respond_to?(:tty?) && output.tty?
517
+ end
518
+
519
+ private
520
+
521
+ # Execute a block on the proper terminal line if the spinner is running
522
+ # under a multispinner. Otherwise, execute the block on the current line.
523
+ #
524
+ # @api private
525
+ def execute_on_line
526
+ if @multispinner
527
+ @multispinner.synchronize do
528
+ if @first_run
529
+ @row ||= @multispinner.next_row
530
+ yield if block_given?
531
+ output.print "\n"
532
+ @first_run = false
533
+ else
534
+ lines_up = (@multispinner.rows + 1) - @row
535
+ output.print TTY::Cursor.save
536
+ output.print TTY::Cursor.up(lines_up)
537
+ yield if block_given?
538
+ output.print TTY::Cursor.restore
539
+ end
540
+ end
541
+ elsif block_given?
542
+ yield
543
+ end
544
+ end
545
+
546
+ # Write data out to output
547
+ #
548
+ # @return [nil]
549
+ #
550
+ # @api private
551
+ def write(data, clear_first = false)
552
+ return unless tty? # write only to terminal
553
+
554
+ execute_on_line do
555
+ output.print(TTY::Cursor.column(1)) if clear_first
556
+ # If there's a top level spinner, print with inset
557
+ characters_in = @multispinner.line_inset(@row) if @multispinner
558
+ output.print("#{characters_in}#{data}")
559
+ output.flush
560
+ end
561
+ end
562
+
563
+ # Emit callback
564
+ #
565
+ # @api private
566
+ def emit(name, *args)
567
+ @callbacks[name].each do |callback|
568
+ callback.call(*args)
569
+ end
570
+ end
571
+
572
+ # Find frames by token name
573
+ #
574
+ # @param [Symbol] token
575
+ # the name for the frames
576
+ #
577
+ # @return [Array, String]
578
+ #
579
+ # @api private
580
+ def fetch_format(token, property)
581
+ unless FORMATS.key?(token)
582
+ raise ArgumentError, "Unknown format token `:#{token}`"
583
+ end
584
+
585
+ FORMATS[token][property]
586
+ end
587
+
588
+ # Replace any token inside string
589
+ #
590
+ # @param [String] string
591
+ # the string containing tokens
592
+ #
593
+ # @return [String]
594
+ #
595
+ # @api private
596
+ def replace_tokens(string)
597
+ data = string.dup
598
+ @tokens.each do |name, val|
599
+ data.gsub!(/:#{name}/, val.to_s)
600
+ end
601
+ data
602
+ end
603
+ end # Spinner
604
+ end # TTY
@@ -0,0 +1,2 @@
1
+ require_relative "tty/spinner"
2
+ require_relative "tty/spinner/multi"
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ if ENV["COVERAGE"] == "true"
4
+ require "simplecov"
5
+ require "coveralls"
6
+
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
8
+ SimpleCov::Formatter::HTMLFormatter,
9
+ Coveralls::SimpleCov::Formatter
10
+ ])
11
+
12
+ SimpleCov.start do
13
+ command_name "spec"
14
+ add_filter "spec"
15
+ end
16
+ end
17
+
18
+ require "tty-spinner"
19
+ require "stringio"
20
+
21
+ class StringIO
22
+ def tty?
23
+ true
24
+ end
25
+ end
26
+
27
+ RSpec.configure do |config|
28
+ config.expect_with :rspec do |expectations|
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ config.mock_with :rspec do |mocks|
33
+ mocks.verify_partial_doubles = true
34
+ end
35
+
36
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
37
+ config.disable_monkey_patching!
38
+
39
+ # This setting enables warnings. It's recommended, but in some cases may
40
+ # be too noisy due to issues in dependencies.
41
+ config.warnings = true
42
+
43
+ if config.files_to_run.one?
44
+ config.default_formatter = "doc"
45
+ end
46
+
47
+ config.profile_examples = 2
48
+
49
+ config.order = :random
50
+
51
+ Kernel.srand config.seed
52
+ end
@@ -0,0 +1,25 @@
1
+ RSpec.describe TTY::Spinner, "#auto_spin" do
2
+ let(:output) { StringIO.new("", "w+") }
3
+
4
+ it "starts and auto spins" do
5
+ spinner = TTY::Spinner.new(output: output, interval: 100)
6
+ allow(spinner).to receive(:spin)
7
+
8
+ spinner.auto_spin
9
+ sleep 0.1
10
+ spinner.stop
11
+
12
+ expect(spinner).to have_received(:spin).at_least(5).times
13
+ end
14
+
15
+ it "restores cursor when erorr is raised" do
16
+ spinner = TTY::Spinner.new(output: output, hide_cursor: true)
17
+
18
+ spinner.auto_spin {
19
+ raise "boom"
20
+ }
21
+
22
+ output.rewind
23
+ expect(output.read).to start_with("\e[?25l").and end_with("\e[?25h")
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ RSpec.describe TTY::Spinner, ":clear" do
2
+ let(:output) { StringIO.new("", "w+") }
3
+
4
+ it "clears output when done" do
5
+ spinner = TTY::Spinner.new(clear: true, output: output)
6
+ 3.times { spinner.spin }
7
+ spinner.stop("Done!")
8
+ output.rewind
9
+ expect(output.read).to eq([
10
+ "\e[1G|",
11
+ "\e[1G/",
12
+ "\e[1G-",
13
+ "\e[0m\e[2K\e[1G"
14
+ ].join)
15
+ end
16
+ end