planter-cli 0.0.4 → 3.0.2

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.gitmodules +3 -0
  4. data/.rubocop.yml +1 -1
  5. data/CHANGELOG.md +31 -0
  6. data/README.md +33 -4
  7. data/Rakefile +52 -16
  8. data/bin/plant +2 -2
  9. data/docker/Dockerfile +2 -4
  10. data/docker/Dockerfile-2.6 +4 -6
  11. data/docker/Dockerfile-2.7 +4 -6
  12. data/docker/Dockerfile-3.0 +4 -5
  13. data/docker/Dockerfile-3.3 +11 -0
  14. data/docker/sources.list +11 -0
  15. data/lib/planter/array.rb +17 -0
  16. data/lib/planter/fileentry.rb +5 -1
  17. data/lib/planter/filelist.rb +13 -4
  18. data/lib/planter/hash.rb +17 -1
  19. data/lib/planter/plant.rb +5 -3
  20. data/lib/planter/prompt.rb +5 -5
  21. data/lib/planter/string.rb +22 -0
  22. data/lib/planter/tag.rb +91 -0
  23. data/lib/planter/version.rb +1 -1
  24. data/lib/planter.rb +34 -18
  25. data/lib/tty-spinner/.editorconfig +9 -0
  26. data/lib/tty-spinner/.github/FUNDING.yml +1 -0
  27. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/BUG_REPORT.md +31 -0
  28. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +23 -0
  29. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/config.yml +5 -0
  30. data/lib/tty-spinner/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  31. data/lib/tty-spinner/.github/workflows/ci.yml +59 -0
  32. data/lib/tty-spinner/.gitignore +14 -0
  33. data/lib/tty-spinner/.rspec +2 -0
  34. data/lib/tty-spinner/.rubocop.yml +78 -0
  35. data/lib/tty-spinner/CHANGELOG.md +151 -0
  36. data/lib/tty-spinner/CODE_OF_CONDUCT.md +132 -0
  37. data/lib/tty-spinner/Gemfile +17 -0
  38. data/lib/tty-spinner/LICENSE.txt +22 -0
  39. data/lib/tty-spinner/README.md +581 -0
  40. data/lib/tty-spinner/Rakefile +10 -0
  41. data/lib/tty-spinner/appveyor.yml +33 -0
  42. data/lib/tty-spinner/bin/console +14 -0
  43. data/lib/tty-spinner/bin/setup +8 -0
  44. data/lib/tty-spinner/demo.gif +0 -0
  45. data/lib/tty-spinner/examples/auto_spin.rb +10 -0
  46. data/lib/tty-spinner/examples/basic.rb +10 -0
  47. data/lib/tty-spinner/examples/clear.rb +11 -0
  48. data/lib/tty-spinner/examples/color.rb +14 -0
  49. data/lib/tty-spinner/examples/error.rb +11 -0
  50. data/lib/tty-spinner/examples/formats.rb +13 -0
  51. data/lib/tty-spinner/examples/hide_cursor.rb +14 -0
  52. data/lib/tty-spinner/examples/log.rb +13 -0
  53. data/lib/tty-spinner/examples/multi/basic.rb +15 -0
  54. data/lib/tty-spinner/examples/multi/basic_top_level.rb +15 -0
  55. data/lib/tty-spinner/examples/multi/custom_style.rb +28 -0
  56. data/lib/tty-spinner/examples/multi/files.rb +16 -0
  57. data/lib/tty-spinner/examples/multi/jobs.rb +11 -0
  58. data/lib/tty-spinner/examples/multi/multi.rb +19 -0
  59. data/lib/tty-spinner/examples/multi/multi_top_level.rb +20 -0
  60. data/lib/tty-spinner/examples/multi/pause.rb +28 -0
  61. data/lib/tty-spinner/examples/multi/threaded.rb +30 -0
  62. data/lib/tty-spinner/examples/pause.rb +24 -0
  63. data/lib/tty-spinner/examples/run.rb +20 -0
  64. data/lib/tty-spinner/examples/success.rb +11 -0
  65. data/lib/tty-spinner/examples/threaded.rb +13 -0
  66. data/lib/tty-spinner/examples/update.rb +13 -0
  67. data/lib/tty-spinner/lib/tty/spinner/formats.rb +274 -0
  68. data/lib/tty-spinner/lib/tty/spinner/multi.rb +352 -0
  69. data/lib/tty-spinner/lib/tty/spinner/version.rb +7 -0
  70. data/lib/tty-spinner/lib/tty/spinner.rb +604 -0
  71. data/lib/tty-spinner/lib/tty-spinner.rb +2 -0
  72. data/lib/tty-spinner/spec/spec_helper.rb +52 -0
  73. data/lib/tty-spinner/spec/unit/auto_spin_spec.rb +25 -0
  74. data/lib/tty-spinner/spec/unit/clear_spec.rb +16 -0
  75. data/lib/tty-spinner/spec/unit/error_spec.rb +53 -0
  76. data/lib/tty-spinner/spec/unit/events_spec.rb +35 -0
  77. data/lib/tty-spinner/spec/unit/formats_spec.rb +9 -0
  78. data/lib/tty-spinner/spec/unit/frames_spec.rb +31 -0
  79. data/lib/tty-spinner/spec/unit/hide_cursor_spec.rb +51 -0
  80. data/lib/tty-spinner/spec/unit/job_spec.rb +12 -0
  81. data/lib/tty-spinner/spec/unit/join_spec.rb +10 -0
  82. data/lib/tty-spinner/spec/unit/log_spec.rb +60 -0
  83. data/lib/tty-spinner/spec/unit/multi/auto_spin_spec.rb +32 -0
  84. data/lib/tty-spinner/spec/unit/multi/error_spec.rb +107 -0
  85. data/lib/tty-spinner/spec/unit/multi/line_inset_spec.rb +57 -0
  86. data/lib/tty-spinner/spec/unit/multi/on_spec.rb +11 -0
  87. data/lib/tty-spinner/spec/unit/multi/register_spec.rb +46 -0
  88. data/lib/tty-spinner/spec/unit/multi/spin_spec.rb +101 -0
  89. data/lib/tty-spinner/spec/unit/multi/stop_spec.rb +95 -0
  90. data/lib/tty-spinner/spec/unit/multi/success_spec.rb +108 -0
  91. data/lib/tty-spinner/spec/unit/new_spec.rb +25 -0
  92. data/lib/tty-spinner/spec/unit/pause_spec.rb +43 -0
  93. data/lib/tty-spinner/spec/unit/reset_spec.rb +19 -0
  94. data/lib/tty-spinner/spec/unit/run_spec.rb +30 -0
  95. data/lib/tty-spinner/spec/unit/spin_spec.rb +117 -0
  96. data/lib/tty-spinner/spec/unit/stop_spec.rb +88 -0
  97. data/lib/tty-spinner/spec/unit/success_spec.rb +53 -0
  98. data/lib/tty-spinner/spec/unit/tty_spec.rb +8 -0
  99. data/lib/tty-spinner/spec/unit/update_spec.rb +85 -0
  100. data/lib/tty-spinner/tasks/console.rake +11 -0
  101. data/lib/tty-spinner/tasks/coverage.rake +11 -0
  102. data/lib/tty-spinner/tasks/spec.rake +29 -0
  103. data/lib/tty-spinner/tty-spinner.gemspec +36 -0
  104. data/planter-cli.gemspec +1 -0
  105. data/scripts/runtests.sh +1 -1
  106. data/spec/cli_spec.rb +27 -0
  107. data/spec/planter/hash_spec.rb +27 -0
  108. data/spec/planter_spec.rb +15 -0
  109. data/spec/spec_helper.rb +26 -0
  110. data/spec/templates/test/%%project:snake%%.rtf +10 -0
  111. data/spec/templates/test/Rakefile +6 -0
  112. data/spec/templates/test/_planter.yml +3 -6
  113. data/spec/templates/test/test.rb +5 -0
  114. data/src/_README.md +20 -3
  115. metadata +108 -3
@@ -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