# Whirly 😀 [![[version]](https://badge.fury.io/rb/whirly.svg)](https://badge.fury.io/rb/whirly)  [<img src="https://github.com/janlelis/whirly/workflows/Test/badge.svg" />](https://github.com/janlelis/whirly/actions/workflows/test.yml)

A simple, colorful and customizable terminal spinner library for Ruby. It comes with 24 custom spinners and also includes those from the [cli-spinners](https://github.com/sindresorhus/cli-spinners) project.

## Demonstration

![](whirly.gif)

### Bundled Whirly Spinners

[Play on asciinema](https://asciinema.org/a/88198?size=big)

### Bundled Spinners from CLI Spinners

![](https://raw.githubusercontent.com/sindresorhus/cli-spinners/main/screenshot.gif)

[Play on asciinema](https://asciinema.org/a/9mlcoussb137m32swwuqtb2p1?size=big)

## Setup

Add to your `Gemfile`:

```ruby
gem 'whirly'
gem 'paint' # makes whirly colorful (recommended)
```

## Usage

### Basic Usage

The spinner is shown while the block executes:

```ruby
Whirly.start do
  # do the heavy work here
  sleep 5
end
```

You can update the spinner text from inside the block:

```ruby
Whirly.start do
  Whirly.status = "Set some text to display alongside the spinner symbol"
  sleep 3
  Whirly.status = "Update it"
  sleep 2
end
```

If you want to avoid the block syntax, you can also stop it manually:

```ruby
Whirly.start
sleep 5
Whirly.stop
```

The `start` method takes a lot of options, like which spinner to use or an initial status. See further below for the full description of available options.

```ruby
Whirly.start spinner: "pong", color: false, status: "The Game of Pong" do
  sleep 10
end
```

Also see the [examples directory](https://github.com/janlelis/whirly/tree/main/examples) for example scripts.

### Configuring Whirly

You can pass the same options you would pass to `.start` to `.configure` instead to create a persistent configuration that will be used by `.start`:

```ruby
Whirly.configure spinner: "dots"

Whirly.start do
  sleep 3 # will use dots
end

Whirly.start do
  sleep 3 # will use dots again
end
```

Call `.reset` to restore unconfigured behaviour:

```ruby
Whirly.configure spinner: "dots"

Whirly.reset

Whirly.start do
  sleep 3 # will use default spinner
end
```

## Spinners

### Included Spinners

See [`data/whirly-static-spinnes.json`](https://github.com/janlelis/whirly/blob/main/data/whirly-static-spinners.json), [`lib/whirly/spinners/whirly.rb`](https://github.com/janlelis/whirly/blob/main/lib/whirly/spinners/whirly.rb) and [cli-spinners](https://github.com/sindresorhus/cli-spinners). You can get a demonstration of all bundled spinners by running the [`examples/all_spinners.rb`](https://github.com/janlelis/whirly/blob/main/examples/all_spinners.rb) script.

## All `Whirly.start` / `Whirly.configure` Configuration Options

### Main Options

#### `spinner:`

*Default:* `"whirly"`

You have multiple ways of telling *Whirly* which spinner should be used. You can pass the following to the `spinner:` option:

- The name of a bundled spinner
- An array of spinner frames to use
- A proc which generates the frames dynamically
- A full spinner hash object ([explained below](https://github.com/janlelis/whirly#full-spinner-hash-format))

#### `status:`

*Default:* None

Allows you to directly set the first status text to display alongside the spinner icon.

#### `interval:`

*Default:* `100`

The number of milliseconds between changing to the next spinner icon frame.

### Advanced Options

#### `ambiguous_characters_width:`

*Default:* `1`

If set to `2`, ambiguous Unicode charatcers will be treated as 2 colums wide. See [unicode-display_width](https://github.com/janlelis/unicode-display_width) for more details.

#### `ansi_escape_mode:`

*Default:* `"restore"`

Can be set to `"line"` to use an different way of producing ANSI escape sequences necessary (experimental).

#### `append_newline:`

*Default:* `true`

When the Whirly block is over (or `.stop` was called), a `"\n"` will be outputted. Change to `false` to prevent this.

#### `color:`

*Default:* `!!defined?(Paint)`

This option is responsible for displaying the spinner icon in random colors. Set to `false` if you do not want this. Related option `:color_change_rate`.

#### `color_change_rate:`

*Default:* `30`

A value which describes how fast the color of the spinner icon changes.

#### `hide_cursor:`

*Default:* `true`

By default, the terminal cursor gets hidden while displaying the spinner. This also registers an `at_exit` callback, which always restores the cursor when exitting the program. If you do not want to hide the cursor, change this option to `false`.

#### `mode:`

*Default:* `"linear"`

Instructs Whirly to play the frames in a different order. Possible values: `"linear"`, `"reverse"`, `"swing"`, and `"random"`. See [spinner format section](https://github.com/janlelis/whirly#mode-1) for more details.

#### `non_tty:`

*Default:* `false`

Whirly only gets activated if the current process appears to be a real terminal. If you want to activate it for non-terimnals, set this option to `true`.

#### `position:`

*Default:* `"normal"`

You can set this to `"below"` to let Whirly appear one line below its normal position.

#### `remove_after_stop:`

*Default:* `false`

Causes the last frame to be removed after the spinner stopped.

#### `stop:`

*Default:* None

You can pass a custom frame to be used to end the animation, for example:

```ruby
Whirly.start spinner: "clock", interval: 1000, stop: "⏰" do
  sleep 12
end
```

#### `spinner_packs:`

*Default:* `[:whirly, :cli]`

Whirly comes with spinners from different sources. This options defines which sources to consider (the value refers to an uppercased child constant of `Whirly::Spinners`) and in which order.

#### `stream:`

*Default:* `$stdout`

You can pass in an [IO](https://ruby-doc.org/core/IO.html)-like object, if you want to display *Whirly* on an other stream than `$stdout`.

## Full Spinner Hash Format

A full spinner is defined by a hash which can have the following key-value pairs. Please note that in order to keep the format more portable, all keys are strings and not Ruby symbols. Except for `"frames"` and `"proc"`, all options are overwritable when starting/configuring Whirly. See the included spinners for example definitions of spinners.

### `"frames"`

An [Array](https://ruby-doc.org/core/Array.html) or [Enumerable](https://ruby-doc.org/core/Enumerable.html) of strings that will be used as the spinner icon.

### `"proc"`

Instead of using `"frames"`: A proc which will generate the next frame with each call.

### `"interval"`

The number of milliseconds between changing to the next spinner icon frame.

### `"mode"`

The order in which frames should be played. It can be one of the following:

- `"linear"`: Cycle through all frames in normal order
- `"reverse"`: Cycle through all frames in reverse order
- `"swing"`: Cycle through all frames in normal order, and then in reverse order, but only play first and last frame once each round
- `"random"`: Play random frames

Please note: While `"linear"` also works with frames that are just an [Enumerable](https://ruby-doc.org/core/Enumerable.html), all other frame modes require the object to be representable as an [Array](https://ruby-doc.org/core/Array.html).

### `"stop"`

A frame to be used to end the spinner icon animation.

## Remarks, Troubleshooting, Caveats

- Interval is milliseconds, but don't rely on exact timing
- Will not do anything if stream is not a real console (or `non_tty: true` is passed)
- Colors not working? Be sure to include the [paint](https://github.com/janlelis/paint/) gem in your Gemfile
- Don't set very short intervals (or it might affect performance substantially)

## MIT License

- Copyright (C) 2016 Jan Lelis <https://janlelis.com>. Released under the MIT license.
- Contains data from cli-spinners:  MIT License, Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
