tty-spinner-0.9.3/0000755000175000017500000000000013620151204013651 5ustar gabstergabstertty-spinner-0.9.3/tty-spinner.gemspec0000644000175000017500000000405213620151204017513 0ustar gabstergabster######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: tty-spinner 0.9.3 ruby lib Gem::Specification.new do |s| s.name = "tty-spinner".freeze s.version = "0.9.3" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "allowed_push_host" => "https://rubygems.org", "bug_tracker_uri" => "https://github.com/piotrmurach/tty-spinner/issues", "changelog_uri" => "https://github.com/piotrmurach/tty-spinner/blob/master/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/tty-spinner", "homepage_uri" => "https://ttytoolkit.org", "source_code_uri" => "https://github.com/piotrmurach/tty-spinner" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Piotr Murach".freeze] s.bindir = "exe".freeze s.date = "2020-01-28" s.description = "A terminal spinner for tasks that have non-deterministic time frame.".freeze s.email = ["piotr@piotrmurach.com".freeze] s.extra_rdoc_files = ["CHANGELOG.md".freeze, "README.md".freeze] s.files = ["CHANGELOG.md".freeze, "LICENSE.txt".freeze, "README.md".freeze, "lib/tty-spinner.rb".freeze, "lib/tty/spinner.rb".freeze, "lib/tty/spinner/formats.rb".freeze, "lib/tty/spinner/multi.rb".freeze, "lib/tty/spinner/version.rb".freeze] s.homepage = "https://ttytoolkit.org".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "2.7.6.2".freeze s.summary = "A terminal spinner for tasks that have non-deterministic time frame.".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 0.7"]) else s.add_dependency(%q.freeze, ["~> 0.7"]) end else s.add_dependency(%q.freeze, ["~> 0.7"]) end end tty-spinner-0.9.3/lib/0000755000175000017500000000000013620151204014417 5ustar gabstergabstertty-spinner-0.9.3/lib/tty/0000755000175000017500000000000013620151204015237 5ustar gabstergabstertty-spinner-0.9.3/lib/tty/spinner/0000755000175000017500000000000013620151204016715 5ustar gabstergabstertty-spinner-0.9.3/lib/tty/spinner/version.rb0000644000175000017500000000015213620151204020725 0ustar gabstergabster# frozen_string_literal: true module TTY class Spinner VERSION = "0.9.3" end # Spinner end # TTY tty-spinner-0.9.3/lib/tty/spinner/multi.rb0000644000175000017500000002044613620151204020402 0ustar gabstergabster# frozen_string_literal: true require 'monitor' require 'forwardable' require_relative '../spinner' module TTY class Spinner # Used for managing multiple terminal spinners # # @api public class Multi include Enumerable include MonitorMixin extend Forwardable def_delegators :@spinners, :each, :empty?, :length DEFAULT_INSET = { top: Gem.win_platform? ? '+ ' : "\u250c ", middle: Gem.win_platform? ? '|-- ' : "\u251c\u2500\u2500 ", bottom: Gem.win_platform? ? '|__ ' : "\u2514\u2500\u2500 " } # The current count of all rendered rows # # @api public attr_reader :rows # Initialize a multispinner # # @example # spinner = TTY::Spinner::Multi.new # # @param [String] message # the optional message to print in front of the top level spinner # # @param [Hash] options # @option options [Hash] :style # keys :top :middle and :bottom can contain Strings that are used to # indent the spinners. Ignored if message is blank # @option options [Object] :output # the object that responds to print call defaulting to stderr # @option options [Boolean] :hide_cursor # display or hide cursor # @option options [Boolean] :clear # clear ouptut when finished # @option options [Float] :interval # the interval for auto spinning # # @api public def initialize(*args) super() @options = args.last.is_a?(::Hash) ? args.pop : {} message = args.empty? ? nil : args.pop @inset_opts = @options.delete(:style) { DEFAULT_INSET } @rows = 0 @spinners = [] @spinners_count = 0 @top_spinner = nil @last_spin_at = nil unless message.nil? @top_spinner = register(message, observable: false, row: next_row) end @callbacks = { success: [], error: [], done: [], spin: [] } end # Register a new spinner # # @param [String, TTY::Spinner] pattern_or_spinner # the pattern used for creating spinner, or a spinner instance # # @api public def register(pattern_or_spinner, **options, &job) observable = options.delete(:observable) { true } spinner = nil synchronize do spinner = create_spinner(pattern_or_spinner, options) spinner.attach_to(self) spinner.job(&job) if block_given? observe(spinner) if observable @spinners << spinner @spinners_count += 1 if @top_spinner @spinners.each { |sp| sp.redraw_indent if sp.spinning? || sp.done? } end end spinner end # Create a spinner instance # # @api private def create_spinner(pattern_or_spinner, options) case pattern_or_spinner when ::String TTY::Spinner.new( pattern_or_spinner, @options.merge(options) ) when ::TTY::Spinner pattern_or_spinner else raise ArgumentError, "Expected a pattern or spinner, " \ "got: #{pattern_or_spinner.class}" end end # Increase a row count # # @api public def next_row synchronize do @rows += 1 end end # Get the top level spinner if it exists # # @return [TTY::Spinner] the top level spinner # # @api public def top_spinner raise "No top level spinner" if @top_spinner.nil? @top_spinner end # Auto spin the top level spinner & all child spinners # that have scheduled jobs # # @api public def auto_spin raise "No top level spinner" if @top_spinner.nil? jobs = [] @spinners.each do |spinner| if spinner.job? spinner.auto_spin jobs << Thread.new { spinner.execute_job } end end jobs.each(&:join) end # Perform a single spin animation # # @api public def spin raise "No top level spinner" if @top_spinner.nil? synchronize do throttle { @top_spinner.spin } end end # Pause all spinners # # @api public def pause @spinners.dup.each(&:pause) end # Resume all spinners # # @api public def resume @spinners.dup.each(&:resume) end # Find the number of characters to move into the line # before printing the spinner # # @param [Integer] line_no # the current spinner line number for which line inset is calculated # # @return [String] # the inset # # @api public def line_inset(line_no) return '' if @top_spinner.nil? if line_no == 1 @inset_opts[:top] elsif line_no == @spinners_count @inset_opts[:bottom] else @inset_opts[:middle] end end # Check if all spinners are done # # @return [Boolean] # # @api public def done? synchronize do (@spinners - [@top_spinner]).all?(&:done?) end end # Check if all spinners succeeded # # @return [Boolean] # # @api public def success? synchronize do (@spinners - [@top_spinner]).all?(&:success?) end end # Check if any spinner errored # # @return [Boolean] # # @api public def error? synchronize do (@spinners - [@top_spinner]).any?(&:error?) end end # Stop all spinners # # @api public def stop @spinners.dup.each(&:stop) end # Stop all spinners with success status # # @api public def success @spinners.dup.each(&:success) end # Stop all spinners with error status # # @api public def error @spinners.dup.each(&:error) end # Listen on event # # @api public def on(key, &callback) unless @callbacks.key?(key) raise ArgumentError, "The event #{key} does not exist. "\ ' Use :spin, :success, :error, or :done instead' end @callbacks[key] << callback self end private # Check if this spinner should revolve to keep constant speed # matching top spinner interval # # @api private def throttle sleep_time = 1.0 / @top_spinner.interval if @last_spin_at && Time.now - @last_spin_at < sleep_time return end yield if block_given? @last_spin_at = Time.now end # Fire an event # # @api private def emit(key, *args) @callbacks[key].each do |block| block.call(*args) end end # Observe spinner for events to notify top spinner of current state # # @param [TTY::Spinner] spinner # the spinner to listen to for events # # @api private def observe(spinner) spinner.on(:spin, &spin_handler) .on(:success, &success_handler) .on(:error, &error_handler) .on(:done, &done_handler) end # Handle spin event # # @api private def spin_handler proc do spin if @top_spinner emit(:spin) end end # Handle the success state # # @api private def success_handler proc do if success? @top_spinner.success if @top_spinner emit(:success) end end end # Handle the error state # # @api private def error_handler proc do if error? @top_spinner.error if @top_spinner @fired ||= emit(:error) # fire once end end end # Handle the done state # # @api private def done_handler proc do if done? @top_spinner.stop if @top_spinner && !error? && !success? emit(:done) end end end end # MultiSpinner end # Spinner end # TTY tty-spinner-0.9.3/lib/tty/spinner/formats.rb0000644000175000017500000001412013620151204020713 0ustar gabstergabster# frozen_string_literal: true module TTY module Formats FORMATS = { classic: { interval: 10, frames: %w{| / - \\} }, spin: { interval: 10, frames: %w{◴ ◷ ◶ ◵ } }, spin_2: { interval: 10, frames: %w{◐ ◓ ◑ ◒ } }, spin_3: { interval: 10, frames: %w{◰ ◳ ◲ ◱} }, spin_4: { interval: 10, frames: %w{╫ ╪} }, pulse: { interval: 10, frames: %w{⎺ ⎻ ⎼ ⎽ ⎼ ⎻} }, pulse_2: { interval: 15, frames: %w{▁ ▃ ▅ ▆ ▇ █ ▇ ▆ ▅ ▃ } }, pulse_3: { interval: 20, frames: '▉▊▋▌▍▎▏▎▍▌▋▊▉' }, dots: { interval: 10, frames: %w{⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏} }, dots_2: { interval: 10, frames: %w{⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷} }, dots_3: { interval: 10, frames: %w{⠋ ⠙ ⠚ ⠞ ⠖ ⠦ ⠴ ⠲ ⠳ ⠓} }, dots_4: { interval: 10, frames: %w{⠄ ⠆ ⠇ ⠋ ⠙ ⠸ ⠰ ⠠ ⠰ ⠸ ⠙ ⠋ ⠇ ⠆} }, dots_5: { interval: 10, frames: %w{⠋ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋} }, dots_6: { interval: 10, frames: %w{⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠴ ⠲ ⠒ ⠂ ⠂ ⠒ ⠚ ⠙ ⠉ ⠁} }, dots_7: { interval: 10, frames: %w{⠈ ⠉ ⠋ ⠓ ⠒ ⠐ ⠐ ⠒ ⠖ ⠦ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈} }, dots_8: { interval: 10, frames: %w{⠁ ⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈ ⠈} }, dots_9: { interval: 10, frames: %w{⢹ ⢺ ⢼ ⣸ ⣇ ⡧ ⡗ ⡏} }, dots_10: { interval: 10, frames: %w{⢄ ⢂ ⢁ ⡁ ⡈ ⡐ ⡠} }, dots_11: { interval: 10, frames: %w{⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈} }, arrow: { interval: 10, frames: %w{← ↖ ↑ ↗ → ↘ ↓ ↙ } }, arrow_pulse: { interval: 10, frames: [ "▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸" ] }, triangle: { interval: 10, frames: %w{◢ ◣ ◤ ◥} }, arc: { interval: 10, frames: %w{ ◜ ◠ ◝ ◞ ◡ ◟ } }, pipe: { interval: 10, frames: %w{ ┤ ┘ ┴ └ ├ ┌ ┬ ┐ } }, bouncing: { interval: 10, frames: [ "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]" ] }, bouncing_ball: { interval: 10, frames: [ "( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )", "(● )" ] }, bounce: { interval: 10, frames: %w{ ⠁ ⠂ ⠄ ⠂ } }, box_bounce: { interval: 10, frames: %w{ ▌ ▀ ▐ ▄ } }, box_bounce_2: { interval: 10, frames: %w{ ▖ ▘ ▝ ▗ } }, star: { interval: 10, frames: %w{ ✶ ✸ ✹ ✺ ✹ ✷ } }, toggle: { interval: 10, frames: %w{ ■ □ ▪ ▫ } }, balloon: { interval: 10, frames: %w{ . o O @ * } }, balloon_2: { interval: 10, frames: %w{. o O ° O o . } }, flip: { interval: 10, frames: '-◡⊙-◠' }, burger: { interval: 6, frames: %w{ ☱ ☲ ☴ } }, dance: { interval: 10, frames: [">))'>", " >))'>", " >))'>", " >))'>", " >))'>", " <'((<", " <'((<", " <'((<"] }, shark: { interval: 10, frames: [ "▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌" ] }, pong: { interval: 10, frames: [ "▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌" ] } } end # Formats end # TTY tty-spinner-0.9.3/lib/tty/spinner.rb0000644000175000017500000002771513620151204017256 0ustar gabstergabster# frozen_string_literal: true require 'monitor' require 'tty-cursor' require_relative 'spinner/version' require_relative 'spinner/formats' module TTY # Used for creating terminal spinner # # @api public class Spinner include Formats include MonitorMixin # @raised when attempting to join dead thread NotSpinningError = Class.new(StandardError) ECMA_CSI = "\x1b[" MATCHER = /:spinner/ TICK = '✔' CROSS = '✖' CURSOR_LOCK = Monitor.new # The object that responds to print call defaulting to stderr # # @api public attr_reader :output # The current format type # # @return [String] # # @api public attr_reader :format # Whether to show or hide cursor # # @return [Boolean] # # @api public attr_reader :hide_cursor # The message to print before the spinner # # @return [String] # the current message # # @api public attr_reader :message # Tokens for the message # # @return [Hash[Symbol, Object]] # the current tokens # # @api public attr_reader :tokens # The amount of time between frames in auto spinning # # @api public attr_reader :interval # The current row inside the multi spinner # # @api public attr_reader :row # Initialize a spinner # # @example # spinner = TTY::Spinner.new # # @param [String] message # the message to print in front of the spinner # # @param [Hash] options # @option options [String] :format # the spinner format type defaulting to :spin_1 # @option options [Object] :output # the object that responds to print call defaulting to stderr # @option options [Boolean] :hide_cursor # display or hide cursor # @option options [Boolean] :clear # clear ouptut when finished # @option options [Float] :interval # the interval for auto spinning # # @api public def initialize(*args) super() options = args.last.is_a?(::Hash) ? args.pop : {} @message = args.empty? ? ':spinner' : args.pop @tokens = {} @format = options.fetch(:format) { :classic } @output = options.fetch(:output) { $stderr } @hide_cursor = options.fetch(:hide_cursor) { false } @frames = options.fetch(:frames) do fetch_format(@format.to_sym, :frames) end @clear = options.fetch(:clear) { false } @success_mark= options.fetch(:success_mark) { TICK } @error_mark = options.fetch(:error_mark) { CROSS } @interval = options.fetch(:interval) do fetch_format(@format.to_sym, :interval) end @row = options[:row] @callbacks = Hash.new { |h, k| h[k] = [] } @length = @frames.length @thread = nil @job = nil @multispinner= nil reset end # Reset the spinner to initial frame # # @api public def reset synchronize do @current = 0 @done = false @state = :stopped @succeeded = false @first_run = true end end # Notifies the TTY::Spinner that it is running under a multispinner # # @param [TTY::Spinner::Multi] the multispinner that it is running under # # @api private def attach_to(multispinner) @multispinner = multispinner end # Whether the spinner has completed spinning # # @return [Boolean] whether or not the spinner has finished # # @api public def done? @done end # Whether the spinner is spinning # # @return [Boolean] whether or not the spinner is spinning # # @api public def spinning? @state == :spinning end # Whether the spinner is in the success state. # When true the spinner is marked with a success mark. # # @return [Boolean] whether or not the spinner succeeded # # @api public def success? @succeeded == :success end # Whether the spinner is in the error state. This is only true # temporarily while it is being marked with a failure mark. # # @return [Boolean] whether or not the spinner is erroring # # @api public def error? @succeeded == :error end # Register callback # # @param [Symbol] name # the name for the event to listen for, e.i. :complete # # @return [self] # # @api public def on(name, &block) synchronize do @callbacks[name] << block end self end # Start timer and unlock spinner # # @api public def start @started_at = Time.now @done = false reset end # Add job to this spinner # # @api public def job(&work) synchronize do if block_given? @job = work else @job end end end # Execute this spinner job # # @yield [TTY::Spinner] # # @api public def execute_job job.(self) if job? end # Check if this spinner has a scheduled job # # @return [Boolean] # # @api public def job? !@job.nil? end # Start automatic spinning animation # # @api public def auto_spin CURSOR_LOCK.synchronize do start sleep_time = 1.0 / @interval spin @thread = Thread.new do sleep(sleep_time) while @started_at if Thread.current['pause'] Thread.stop Thread.current['pause'] = false end spin sleep(sleep_time) end end end ensure if @hide_cursor write(TTY::Cursor.show, false) end end # Checked if current spinner is paused # # @return [Boolean] # # @api public def paused? !!(@thread && @thread['pause']) end # Pause spinner automatic animation # # @api public def pause return if paused? synchronize do @thread['pause'] = true if @thread end end # Resume spinner automatic animation # # @api public def resume return unless paused? @thread.wakeup if @thread end # Run spinner while executing job # # @param [String] stop_message # the message displayed when block is finished # # @yield automatically animate and finish spinner # # @example # spinner.run('Migrated DB') { ... } # # @api public def run(stop_message = '', &block) job(&block) auto_spin @work = Thread.new { execute_job } @work.join ensure stop(stop_message) end # Duration of the spinning animation # # @return [Numeric] # # @api public def duration @started_at ? Time.now - @started_at : nil end # Join running spinner # # @param [Float] timeout # the timeout for join # # @api public def join(timeout = nil) unless @thread raise(NotSpinningError, 'Cannot join spinner that is not running') end timeout ? @thread.join(timeout) : @thread.join end # Kill running spinner # # @api public def kill synchronize do @thread.kill if @thread end end # Perform a spin # # @return [String] # the printed data # # @api public def spin synchronize do return if @done emit(:spin) if @hide_cursor && !spinning? write(TTY::Cursor.hide) end data = message.gsub(MATCHER, @frames[@current]) data = replace_tokens(data) write(data, true) @current = (@current + 1) % @length @state = :spinning data end end # Redraw the indent for this spinner, if it exists # # @api private def redraw_indent if @hide_cursor && !spinning? write(TTY::Cursor.hide) end write("", false) end # Finish spining # # @param [String] stop_message # the stop message to print # # @api public def stop(stop_message = '') mon_enter return if done? clear_line return if @clear data = message.gsub(MATCHER, next_char) data = replace_tokens(data) if !stop_message.empty? data << ' ' + stop_message end write(data, false) write("\n", false) unless @clear || @multispinner ensure @state = :stopped @done = true @started_at = nil if @hide_cursor write(TTY::Cursor.show, false) end emit(:done) kill mon_exit end # Retrieve next character # # @return [String] # # @api private def next_char if success? @success_mark elsif error? @error_mark else @frames[@current - 1] end end # Finish spinning and set state to :success # # @api public def success(stop_message = '') return if done? synchronize do @succeeded = :success stop(stop_message) emit(:success) end end # Finish spinning and set state to :error # # @api public def error(stop_message = '') return if done? synchronize do @succeeded = :error stop(stop_message) emit(:error) end end # Clear current line # # @api public def clear_line write(ECMA_CSI + '0m' + TTY::Cursor.clear_line) end # Update string formatting tokens # # @param [Hash[Symbol]] tokens # the tokens used in formatting string # # @api public def update(tokens) synchronize do clear_line if spinning? @tokens.merge!(tokens) end end private # Execute a block on the proper terminal line if the spinner is running # under a multispinner. Otherwise, execute the block on the current line. # # @api private def execute_on_line if @multispinner @multispinner.synchronize do if @first_run @row ||= @multispinner.next_row yield if block_given? output.print "\n" @first_run = false else lines_up = (@multispinner.rows + 1) - @row output.print TTY::Cursor.save output.print TTY::Cursor.up(lines_up) yield if block_given? output.print TTY::Cursor.restore end end else yield if block_given? end end # Write data out to output # # @return [nil] # # @api private def write(data, clear_first = false) return unless tty? # write only to terminal execute_on_line do output.print(TTY::Cursor.column(1)) if clear_first # If there's a top level spinner, print with inset characters_in = @multispinner.line_inset(@row) if @multispinner output.print("#{characters_in}#{data}") output.flush end end # Check if IO is attached to a terminal # # return [Boolean] # # @api public def tty? output.respond_to?(:tty?) && output.tty? end # Emit callback # # @api private def emit(name, *args) @callbacks[name].each do |callback| callback.call(*args) end end # Find frames by token name # # @param [Symbol] token # the name for the frames # # @return [Array, String] # # @api private def fetch_format(token, property) if FORMATS.key?(token) FORMATS[token][property] else raise ArgumentError, "Unknown format token `:#{token}`" end end # Replace any token inside string # # @param [String] string # the string containing tokens # # @return [String] # # @api private def replace_tokens(string) data = string.dup @tokens.each do |name, val| data.gsub!(/\:#{name}/, val.to_s) end data end end # Spinner end # TTY tty-spinner-0.9.3/lib/tty-spinner.rb0000644000175000017500000000010413620151204017233 0ustar gabstergabsterrequire_relative 'tty/spinner' require_relative 'tty/spinner/multi' tty-spinner-0.9.3/README.md0000644000175000017500000003437113620151204015140 0ustar gabstergabster
tty logo
# TTY::Spinner [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter] [![Gem Version](https://badge.fury.io/rb/tty-spinner.svg)][gem] [![Build Status](https://secure.travis-ci.org/piotrmurach/tty-spinner.svg?branch=master)][travis] [![Build status](https://ci.appveyor.com/api/projects/status/2i5lx3tvyi5l8x3j?svg=true)][appveyor] [![Maintainability](https://api.codeclimate.com/v1/badges/d5ae2219e194ac99be58/maintainability)][codeclimate] [![Coverage Status](https://coveralls.io/repos/piotrmurach/tty-spinner/badge.svg)][coverage] [![Inline docs](http://inch-ci.org/github/piotrmurach/tty-spinner.svg?branch=master)][inchpages] [gitter]: https://gitter.im/piotrmurach/tty [gem]: http://badge.fury.io/rb/tty-spinner [travis]: http://travis-ci.org/piotrmurach/tty-spinner [appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-spinner [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-spinner/maintainability [coverage]: https://coveralls.io/r/piotrmurach/tty-spinner [inchpages]: http://inch-ci.org/github/piotrmurach/tty-spinner > A terminal spinner for tasks that have non-deterministic time frame. **TTY::Spinner** provides independent spinner component for [TTY](https://github.com/piotrmurach/tty) toolkit. ![](demo.gif) ## Installation Add this line to your application's Gemfile: ```ruby gem 'tty-spinner' ``` And then execute: $ bundle Or install it yourself as: $ gem install tty-spinner ## Contents * [1. Usage](#1-usage) * [2. TTY::Spinner API](#2-ttyspinner-api) * [2.1 spin](#21-spin) * [2.2 auto_spin](#22-auto_spin) * [2.2.1 pause](#221-pause) * [2.2.2 resume](#222-resume) * [2.3 run](#23-run) * [2.4 start](#24-start) * [2.5 stop](#25-stop) * [2.5.1 success](#251-success) * [2.5.2 error](#252-error) * [2.6 update](#26-update) * [2.7 reset](#27-reset) * [2.8 join](#28-join) * [3. Configuration](#3-configuration) * [3.1 :format](#31-format) * [3.2 :frames](#32-frames) * [3.3 :interval](#33-interval) * [3.4 :hide_cursor](#34-hide_cursor) * [3.5 :clear](#35-clear) * [3.6 :success_mark](#36-success_mark) * [3.7 :error_mark](#37-error_mark) * [3.8 :output](#38-output) * [4. Events](#4-events) * [4.1 done](#41-done) * [4.2 success](#42-success) * [4.3 error](#43-error) * [5. TTY::Spinner::Multi API](#5-ttyspinnermulti-api) * [5.1 register](#51-register) * [5.2 auto_spin](#52-auto_spin) * [5.2.1 manual async](#521-manual-async) * [5.2.2 auto async tasks](#522-auto-async-tasks) * [5.3 stop](#53-stop) * [5.3.1 success](#531-success) * [5.3.2 error](#532-error) * [5.4 :style](#54-style) ## 1. Usage **TTY::Spinner** by default uses `:classic` type of formatter and requires no parameters: ```ruby spinner = TTY::Spinner.new ``` In addition you can provide a message with `:spinner` token and format type you would like for the spinning display: ```ruby spinner = TTY::Spinner.new("[:spinner] Loading ...", format: :pulse_2) spinner.auto_spin # Automatic animation with default interval sleep(2) # Perform task spinner.stop('Done!') # Stop animation ``` This would produce animation in your terminal: ```ruby ⎺ Loading ... ``` and when finished output: ```ruby _ Loading ... Done! ``` Use **TTY::Spinner::Multi** to synchronize multiple spinners: ```ruby spinners = TTY::Spinner::Multi.new("[:spinner] top") sp1 = spinners.register "[:spinner] one" # or sp1 = ::TTY::Spinner.new("[:spinner] one") sp2 = spinners.register "[:spinner] two" sp1.auto_spin sp2.auto_spin sleep(2) # Perform work sp1.success sp2.success ``` which when done will display: ```ruby ┌ [✔] top ├── [✔] one └── [✔] two ``` For more usage examples please see [examples directory](https://github.com/piotrmurach/tty-spinner/tree/master/examples) ## 2. TTY::Spinner API ### 2.1 spin The main workhorse of the spinner is the `spin` method. Looping over `spin` method will animate a given spinner. ```ruby loop do spinner.spin end ``` ### 2.2 auto_spin To perform automatic spinning animation use `auto_spin` method like so: ```ruby spinner.auto_spin ``` The speed with which the spinning happens is determined by the `:interval` parameter. All the spinner formats have their default intervals specified ([see](https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb)). ### 2.2.1 pause After calling `auto_spin` you can pause spinner execution: ```ruby spinner.pause ``` ### 2.2.2 resume You can continue any paused spinner: ```ruby spinner.resume ``` ### 2.3 run Use `run` passing a block with a job that will automatically display spinning animation while the block executes and finish animation when the block terminates. The block yields a spinner instance. ```ruby spinner.run do |spinner| ... end ``` Optionally you can provide a stop message to display when animation is finished. ```ruby spinner.run('Done!') do |spinner| ... end ``` ### 2.4 start In order to set start time or reuse the same spinner after it has stopped, call `start` method: ```ruby spinner.start ``` ### 2.5 stop In order to stop the spinner call `stop`. This will finish drawing the spinning animation and return to new line. ```ruby spinner.stop ``` You can further pass a message to print when animation is finished. ```ruby spinner.stop('Done!') ``` #### 2.5.1 success Use `success` call to stop the spinning animation and replace the spinning symbol with check mark character to indicate successful completion. ```ruby spinner = TTY::Spinner.new("[:spinner] Task name") spinner.success('(successful)') ``` This will produce: ``` [✔] Task name (successful) ``` #### 2.5.2 error Use `error` call to stop the spinning animation and replace the spinning symbol with cross character to indicate error completion. ```ruby spinner = TTY::Spinner.new("[:spinner] Task name") spinner.error('(error)') ``` This will produce: ```ruby [✖] Task name (error) ``` ### 2.6 update Use `update` call to dynamically change label name(s). Provide an arbitrary token name(s) in the message string, such as `:title` ```ruby spinner = TTY::Spinner.new("[:spinner] :title") ``` and then pass token name and value: ```ruby spinner.update(title: 'Downloading file1') ``` next start animation: ```ruby spinner.run { ... } # => | Downloading file1 ``` Once animation finishes you can kick start another one with a different name: ```ruby spinner.update(title: 'Downloading file2') spinner.run { ... } ``` ### 2.7 reset In order to reset the spinner to its initial frame do: ```ruby spinner.reset ``` ### 2.8 join One way to wait while the spinning animates is to join the thread started with `start` method: ```ruby spinner.join ``` Optionally you can provide timeout: ```ruby spinner.join(0.5) ``` ## 3. Configuration There are number of configuration options that can be provided to customise the behaviour of a spinner. ### 3.1 :format Use one of the predefined spinner styles by passing the formatting token `:format` ```ruby spinner = TTY::Spinner.new(format: :pulse_2) ``` All spinner formats that **TTY::Spinner** accepts are defined in [/lib/tty/spinner/formats.rb](https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb) If you wish to see all available formats in action run the `formats.rb` file in examples folder like so: ```ruby bundle exec ruby examples/formats.rb ``` ### 3.2 :frames If you wish to use custom formatting use the `:frames` option with either `array` or `string` of characters. ```ruby spinner = TTY::Spinner.new(frames: [".", "o", "0", "@", "*"]) ``` ### 3.3 :interval The `:interval` option accepts `integer` representing number of `Hz` units, for instance, frequency of 10 will mean that the spinning animation will be displayed 10 times per second. ```ruby spinner = TTY::Spinner.new(interval: 20) # 20 Hz (20 times per second) ``` ### 3.4 :hide_cursor Hides cursor when spinning animation performs. Defaults to `false`. ```ruby spinner = TTY::Spinner.new(hide_cursor: true) ``` ### 3.5 :clear After spinner is finished clears its output. Defaults to `false`. ```ruby spinner = TTY::Spinner.new(clear: true) ``` ### 3.6 :success_mark To change marker indicating successful completion use the `:success_mark` option: ```ruby spinner = TTY::Spinner.new(success_mark: '+') ``` ### 3.7 :error_mark To change marker indicating error completion use the `:error_mark` option: ```ruby spinner = TTY::Spinner.new(error_mark: 'x') ``` ### 3.8 :output The spinner only outputs to a console and when output is redirected to a file or a pipe it does nothing. This is so, for example, your error logs do not overflow with spinner output. You can change where console output is streamed with `:output` option: ```ruby spinner = TTY::Spinner.new(output: $stdout) ``` The output stream defaults to `stderr`. ## 4. Events **TTY::Spinner** emits `:done`, `:success` and `:error` event types when spinner is stopped. ### 4.1 done This event is emitted irrespective of the completion method. In order to listen for this event you need to register callback: ```ruby spinner.on(:done) { ... } ``` ### 4.2 success This event is fired when `success` call is made. In order to respond to the event, you need to register callback: ```ruby spinner.on(:success) { ... } ``` ### 4.3 error This event is fired when `error` completion is called. In order to respond to the event, you need to register callback: ```ruby spinner.on(:error) { ... } ``` ## 5. TTY::Spinner::Multi API ### 5.1 register Create and register a `TTY::Spinner` under the multispinner ```ruby new_spinner = multi_spinner.register("[:spinner] Task 1 name", options) # or # spinner = ::TTY::Spinner.new("[:spinner] one") # sp1 = multi_spinner.register(spinner) ``` If no options are given it will use the options given to the multi_spinner when it was initialized to create the new spinner. If options are passed, they will override any options given to the multi spinner. ### 5.2 auto_spin To create a top level spinner that tracks activity of all the registered spinners, the multispinner has to have been given a message on initialization: ```ruby multi_spinner = TTY::Spinner::Multi.new("[:spinner] Top level spinner") ``` The top level multi spinner will perform spinning animation automatically when at least one of the registered spinners starts spinning. If you register spinners without any tasks then you will have to manually control when the `multi_spinner` finishes by calling `stop`, `success` or `error` (see [manual](#521-manual-async)). Alternatively, you can register spinners with tasks that will automatically animate and finish spinners when respective tasks are done (see [async tasks](#522-auto-async-tasks)). The speed with which the spinning happens is determined by the `:interval` parameter. All the spinner formats have their default intervals specified ([see](https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb)). #### 5.2.1 manual async In case when you wish to have full control over multiple spinners, you will need to perform all actions manually. For example, create a multi spinner that will track status of all registered spinners: ```ruby multi_spinner = TTY::Spinner::Multi.new("[:spinner] top") ``` and then register spinners with their formats: ``` spinner_1 = spinners.register "[:spinner] one" spinner_2 = spinners.register "[:spinner] two" ``` Once registered, you can set spinners running in separate threads: ```ruby spinner_1.auto_spin spinner_2.auto_spin ``` Finally, you need to stop each spinner manually, in our case we mark the second spinner as failure which in turn will stop the top level multi spinner automatically and mark it as failure: ```ruby spinner_1.success spinner_2.error ``` The result may look like this: ```ruby ┌ [✖] top ├── [✔] one └── [✖] two ``` #### 5.2.2 auto async tasks In case when you wish to execute async tasks and update individual spinners automatically, in any order, about their task status use `#register` and pass additional block parameter with the job to be executed. For example, create a multi spinner that will track status of all registered spinners: ```ruby multi_spinner = TTY::Spinner::Multi.new("[:spinner] top") ``` and then register spinners with their respective tasks: ```ruby multi_spinner.register("[:spinner] one") { |sp| sleep(2); sp.success('yes 2') } multi_spinner.register("[:spinner] two") { |sp| sleep(3); sp.error('no 2') } ``` Finally, call `#auto_spin` to kick things off: ```ruby multi_spinner.auto_spin ``` If any of the child spinner stops with error then the top level spinner will be marked as failure. ### 5.3 stop In order to stop the multi spinner call `stop`. This will stop the top level spinner, if it exists, and any sub-spinners still spinning. ```ruby multi_spinner.stop ``` #### 5.3.1 success Use `success` call to stop the spinning animation and replace the spinning symbol with a check mark character to indicate successful completion. This will also call `#success` on any sub-spinners that are still spinning. ```ruby multi_spinner.success ``` #### 5.3.2 error Use `error` call to stop the spinning animation and replace the spinning symbol with cross character to indicate error completion. This will also call `#error` on any sub-spinners that are still spinning. ```ruby multi_spinner.error ``` ### 5.4 :style In addition to all [configuration options](#3-configuration) you can style multi spinner like so: ```ruby multi_spinner = TTY::Spinner::Multi.new("[:spinner] parent", style: { top: '. ' middle: '|-> ' bottom: '|__ ' }) ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-spinner. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 1. Fork it ( https://github.com/piotrmurach/tty-spinner/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request ## Copyright Copyright (c) 2014 Piotr Murach. See LICENSE for further details. tty-spinner-0.9.3/LICENSE.txt0000644000175000017500000000205513620151204015476 0ustar gabstergabsterCopyright (c) 2014 Piotr Murach MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tty-spinner-0.9.3/CHANGELOG.md0000644000175000017500000001102413620151204015460 0ustar gabstergabster# Change log ## [v0.9.3] - 2020-01-28 ### Changed * Change gemspec to add metadata, remove test artefacts and load version directly ## [v0.9.2] - 2019-12-08 ### Fixed * Fix multi spinner cursor hiding by @benklop ## [v0.9.1] - 2019-05-29 ### Changed * Change bundler to remove version limit * Change to update tty-cursor dependency ## [v0.9.0] - 2018-12-01 ### Changed * Change tty-cursor dependency * Change to Ruby >= 2.0 * Change to freeze all string literals * Change #execute_job to stop evaluating in spinner context and just execute the job * Change #register to accept a spinner instance by Shane Cavanaugh(@shanecav84) ### Fixed * Fix to remove a stray single quote in spin_4 by Kristofer Rye(@rye) * Fix Multi#line_inset to correctly assign styling in threaded environment * Fix #stop & #auto_spin to always restore hidden cursor if enabled * Fix deadlock when registering multi spinners ## [v0.8.0] - 2018-01-11 ### Added * Add new formats :bounce, :burger, :dance, :dot_2, ..., dot_11, :shark, :pong ### Changed * Change to only output to a console and stop output to a file, pipe etc... ### Fixed * Fix spinner #stop to clear line before printing final message ## [v0.7.0] - 2017-09-11 ### Added * Add :spin event type and emit from TTY::Spinner#spin ### Changed * Change to automatically spin top level multi spinner when registered spinners spin * Remove unnecessary checks for top spinner in multi spinner #stop, #success, #error ### Fixed * Fix multi spinner #observe to only listen for events from registered spinners ## [v0.6.0] - 2017-09-07 ### Changed * Change TTY::Spinner::Multi to render registered spinners at row position at point of rendering and not registration ### Fixed * Fix handling of multi spinner events * Fix multi spinner display for unicode inset characters ## [v0.5.0] - 2017-08-09 ### Added * Add TTY::Spinner::Multi to allow for parallel spinners executation by Austin Blatt[@austb] * Add formatting for multi spinner display by Austin Blatt[@austb] * Add ability to add and execute jobs for single and multi spinners * Add abilty to register multi spinners with async jobs * Add #pause and #resume for single and multispinner ### Changed * Change to unify success category to mark spinner as succeded or errored * Change Spinner to be thread safe ### Fixed * Stop firing events when a spinner is stopped ## [v0.4.1] - 2016-08-07 ### Changed * Change #update to clear output when in spinning state ## [v0.4.0] - 2016-08-07 ### Added * Add #auto_spin to automatically displaying spinning animation ### Changed * Change #start to setup timer and reset done state ## [v0.3.0] - 2016-07-14 ### Added * Add #run to automatically execute job with spinning animation by @Thermatix * Add #update to allow for dynamic label name replacement ### Fixed * Fixed cursor hiding for success and error calls by @m-o-e * Fix #join call to define actual error * Fix #stop to print only once when finished ## [v0.2.0] - 2016-03-13 ### Added * Add new spinner formats by @rlqualls * Add ability to specify custom frames through :frames option * Add :clear option for removing spinner output when done * Add #success and #error calls for stopping spinner * Add :done, :success, :error completion events * Add :success_mark & :error_mark to allow changing markers * Add :interval for automatic spinning duration * Add #start, #join and #kill for automatic spinner animation ### Changed * Change message formatting, use :spinner token to customize message * Change format for defining spinner formats and intervals ## [v0.1.0] - 2014-11-15 * Initial implementation and release [v0.9.3]: https://github.com/piotrmurach/tty-spinner/compare/v0.9.2...v0.9.3 [v0.9.2]: https://github.com/piotrmurach/tty-spinner/compare/v0.9.1...v0.9.2 [v0.9.1]: https://github.com/piotrmurach/tty-spinner/compare/v0.9.0...v0.9.1 [v0.9.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.8.0...v0.9.0 [v0.8.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.7.0...v0.8.0 [v0.7.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.6.0...v0.7.0 [v0.6.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.5.0...v0.6.0 [v0.5.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.4.1...v0.5.0 [v0.4.1]: https://github.com/piotrmurach/tty-spinner/compare/v0.4.0...v0.4.1 [v0.4.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/piotrmurach/tty-spinner/compare/v0.1.0