Initial commit

pull/1/head
ad4mx 2022-07-15 19:59:22 +02:00
commit 78ed43eb57
14 changed files with 1740 additions and 0 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

29
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Lint
run: cargo clippy
- name: Format
run: cargo fmt
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Run examples
run: cargo run --example simple

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "spinoff"
version = "0.1.0"
edition = "2021"
author = "ad4m"
description = "🔨 Simple to use Rust library for displaying spinners in the terminal."
license = "MIT"
homepage = "https://github.com/ad4mx/spinoff"
repository = "https://github.com/ad4mx/spinoff"
readme = "README.md"
keywords = ["spinner", "spin", "loader", "term", "terminal"]
categories = ["command-line-interface"]
include = ["src/**/*", "README.md"]
[dependencies]
lazy_static = "1.4.0"
maplit = "1.0.2"
yansi = "0.5.1"
strum = { version = "0.24.0", features = ["derive"] }

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 ad4m
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.

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# spinoff
`spinoff` is a simple to use library for displaying spinners in the terminal, with plenty of features and options.
[![Cargo version](https://img.shields.io/crates/v/spinoff.svg)](https://crates.io/crates/spinoff) [![Crates.io](https://img.shields.io/crates/l/spinoff.svg)] [![Crates.io](https://img.shields.io/crates/d/spinoff.svg)](https://crates.io/crates/spinoff)
## Usage
```rust
use spinoff::Spinners;
use std::thread::sleep;
use std::time::Duration;
let spinner = spinoff::new(Spinners::Dots, "Loading...", "Blue".into()); // Can also be Some("blue") or None
sleep(Duration::from_secs(3));
spinner.success("Done!");
```
## Documentation
* All documentation can be found on the [Docs.rs page](https://docs.rs/spinoff/latest/spinoff/).
* If you want to see all the available `Spinner` options, check the [`Spinners`](src/utils/spinner_enum.rs) enum.
## Examples
To run some of the included examples, use:
```bash
cargo run --example all_spinners
```
```bash
cargo run --example simple
```
## Contributing
Any contributions to this crate are highly appreciated. If you have any ideas/suggestions/bug fixes, please open an issue or pull request.
## License
This crate is licensed under the [MIT license](LICENSE).

12
examples/all_spinners.rs Normal file
View File

@ -0,0 +1,12 @@
use spinoff::Spinners;
use std::thread::sleep;
use std::time::Duration;
use strum::IntoEnumIterator;
fn main() {
for spinner in Spinners::iter() {
let spin = spinoff::new(spinner, "", None);
sleep(Duration::from_secs(1));
spin.clear();
}
println!("Done!");
}

8
examples/simple.rs Normal file
View File

@ -0,0 +1,8 @@
use spinoff::Spinners;
use std::thread::sleep;
use std::time::Duration;
fn main() {
let sp = spinoff::new(Spinners::Dots, "Loading...", None);
sleep(Duration::from_millis(800));
sp.success("Success!");
}

View File

@ -0,0 +1,9 @@
use spinoff::Spinners;
use std::thread::sleep;
use std::time::Duration;
fn main() {
let sp = spinoff::new(Spinners::Dots, "Loading...", None);
sleep(Duration::from_secs(5));
sp.stop_and_persist("🍕", "Pizza!", "yellow".into());
}

281
src/lib.rs Normal file
View File

@ -0,0 +1,281 @@
//! ## spinoff
//!
//! `spinoff` is a simple library for displaying spinners in the terminal.
//! ### Usage
//!
//! ```rust
//! use spinoff::Spinners;
//! use std::thread::sleep;
//! use std::time::Duration;
//!
//! let sp = spinoff::new(Spinners::Dots, "Loading...", None);
//! sleep(Duration::from_millis(800));
//! sp.success("Success!");
//!
//! ```
//! ### Spinners
//! There are over 80+ spinners available in the `Spinners` enum.
//!
//! ### Colors
//! You can also color your spinners without any hassle. Simply pass a color to the `color` option.
//! There are 6 colors available: `blue`, `green`, `red`, `yellow`, `cyan`, `white`.
//! Don't want any of that? Simply pass `None` to the `color` option.
//!
#![allow(clippy::needless_return)]
mod utils;
use crate::utils::printer::{delete_last_line, init_color};
use crate::utils::spinner_data::SPINNER_FRAMES;
pub use crate::utils::spinner_enum::Spinners;
use std::io::{stdout, Write};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::thread;
// Using this type for better readability.
type StringLiteral = &'static str;
/// All methods for the spinner are implemented in this struct.
/// This struct has an `Arc<AtomicBool>` field, which is later used in the `stop` type methods to stop the thread printing the spinner.
/// The `msg` field is needed for the `clear` and `stop` methods for knowing how many spaces to print to clean up the previous spinner.
pub struct Spinner {
still_spinning: Arc<AtomicBool>,
msg: StringLiteral,
}
/// Create a new spinner.
///
/// # Arguments
/// * `spinner_type` - The spinner to use.
/// * `msg` - The message to display.
/// * `color` - The color of the spinner.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", "blue".into()); // Color can also be `None` or `Some("blue")`
/// sleep(Duration::from_millis(800));
/// sp.clear();
/// ```
/// # Panics
/// * `Invalid color` - If the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
/// * `Invalid spinner` - If the spinner is not one of those belonging to the `Spinners` enum.
///
/// # Notes
/// * The spinner immediately starts spinning upon creation.
pub fn new(spinner_type: Spinners, msg: StringLiteral, color: Option<StringLiteral>) -> Spinner {
let spinner_data = SPINNER_FRAMES
.get(&spinner_type)
.expect("invalid spinner: expected variant of Spinners enum");
let mut stdout = stdout();
let mut i = 0;
let still_spinning = Arc::new(AtomicBool::new(true));
// Clone the atomic bool so that we can use it in the thread and return the original one later.
let still_spinning_cloned = still_spinning.clone();
// We use atomic bools to make the thread stop itself when the `spinner.stop()` method is called.
thread::spawn(move || {
while still_spinning_cloned.load(std::sync::atomic::Ordering::Relaxed) {
let text = format!(
"\r{} {}",
init_color(color, spinner_data.frames[i].to_string()),
msg
);
stdout.write_all(text.as_bytes()).unwrap();
stdout.flush().unwrap();
thread::sleep(std::time::Duration::from_millis(
spinner_data.interval as u64,
));
i = (i + 1) % spinner_data.frames.len();
}
});
// Return a Spinner struct so we can implement methods on it instead of `spinoff::stop()` etc.
let spinner = Spinner {
still_spinning,
msg,
};
return spinner;
}
impl Spinner {
/// Stop the spinner.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.stop();
/// ```
/// # Notes
/// * The spinner will be deleted after this method is called, the message will remain though.
/// * This method also sets the `still_spinning` atomic bool to `false`, which stops the spinner thread.
/// * This method cannot be called if the spinner is already stopped.
///
pub fn stop(self) {
self.still_spinning
.store(false, std::sync::atomic::Ordering::Relaxed);
print!("\r");
println!("{} ", self.msg);
}
/// Stops the spinner and prints a message on a new line.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.stop_with_message("Bye", "red".into());
/// ```
///
/// # Panics
/// * The method will panic if the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
///
/// # Notes
/// * This method cannot be called if the spinner is already stopped.
///
pub fn stop_with_message(self, msg: StringLiteral, color: Option<StringLiteral>) {
self.stop();
println!("{}", init_color(color, msg.into()));
}
/// Deletes the spinner and message and prints a new line with a symbol and message.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.stop_and_persist("🍕", "Pizza!", None);
/// ```
///
/// # Panics
/// * The method will panic if the color is not one of the following: `blue`, `green`, `red`, `yellow`, `cyan`, `white` or `None`.
///
/// # Notes
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the success message.
/// * This method cannot be called if the spinner is already stopped.
pub fn stop_and_persist(self, symbol: StringLiteral, msg: StringLiteral, color: Option<StringLiteral>) {
self.clear();
println!("{} {}", init_color(color, symbol.into()), &msg);
}
/// Deletes the last line of the terminal and prints a success symbol with a message.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.success("Success!");
/// ```
///
/// # Notes
/// * This method cannot be called if the spinner is already stopped.
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the success message.
pub fn success(self, msg: StringLiteral) {
self.stop_and_persist("", msg, "green".into());
}
/// Deletes the last line of the terminal and prints a failure symbol with a message.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.fail("Failed!");
/// ```
///
/// # Notes
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the failure message.
/// * This method cannot be called if the spinner is already stopped.
///
pub fn fail(self, msg: StringLiteral) {
self.stop_and_persist("", msg, "red".into());
}
/// Deletes the last line of the terminal and prints a warning symbol with a message.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.warn("Look out!");
/// ```
///
///
/// # Notes
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the warning message.
/// * This method cannot be called if the spinner is already stopped.
pub fn warn(self, msg: StringLiteral) {
self.stop_and_persist("", msg, "yellow".into());
}
/// Deletes the last line of the terminal and prints a new spinner.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let mut sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp = sp.update(Spinners::Dots2, "Goodbye".into(), None);
/// sleep(Duration::from_millis(800));
/// sp.stop();
/// ```
///
/// # Notes
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the new spinner instance.
/// * This method cannot be called if the spinner is already stopped.
///
pub fn update(self, spinner: Spinners, msg: StringLiteral, color: Option<StringLiteral>) -> Spinner {
self.still_spinning
.store(false, std::sync::atomic::Ordering::Relaxed);
delete_last_line(self.msg);
self::new(spinner, msg, color)
}
/// Deletes the last line of the terminal.
///
/// # Example
/// ```rust
/// use spinoff::Spinners;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let mut sp = spinoff::new(Spinners::Dots, "Hello", None);
/// sleep(Duration::from_millis(800));
/// sp.clear();
/// ```
///
/// # Notes
/// * This method will delete the last line of the terminal, so it is recommended to not print anything in between the spinner and the `delete` method call.
/// * This method cannot be called if the spinner is already stopped.
pub fn clear(self) {
self.still_spinning
.store(false, std::sync::atomic::Ordering::Relaxed);
delete_last_line(self.msg);
}
}

3
src/utils/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod printer;
pub mod spinner_data;
pub mod spinner_enum;

25
src/utils/printer.rs Normal file
View File

@ -0,0 +1,25 @@
use yansi::Paint;
use crate::StringLiteral;
pub fn delete_last_line(msg: StringLiteral) {
print!("\r");
for _ in 0..(msg.len() + 30) {
print!(" ");
}
print!("\r");
}
/// Accepts a color option and spinner, returns a Paint<String> object (e.g. `Paint::green(spinner)`)
pub fn init_color(color: Option<&str>, spinner: String) -> Paint<String> {
match color {
Some("blue") => Paint::blue(spinner),
Some("green") => Paint::green(spinner),
Some("red") => Paint::red(spinner),
Some("yellow") => Paint::yellow(spinner),
Some("cyan") => Paint::cyan(spinner),
Some("white") => Paint::new(spinner),
None => Paint::new(spinner),
_ => panic!("invalid color: expected one of the following: blue, green, red, yellow, cyan, white or None"),
}
}

1190
src/utils/spinner_data.rs Normal file

File diff suppressed because it is too large Load Diff

89
src/utils/spinner_enum.rs Normal file
View File

@ -0,0 +1,89 @@
use strum::EnumIter;
/// An enum of all the available spinners.
/// Contains around 80+ spinners.
/// Each variant in this enum is assigned to a HashMap holding it's frames and interval count.
#[derive(Debug, Eq, Hash, PartialEq, EnumIter)]
pub enum Spinners {
Dots,
Dots2,
Dots3,
Dots4,
Dots5,
Dots6,
Dots7,
Dots8,
Dots9,
Dots10,
Dots11,
Dots12,
Dots8Bit,
Line,
Line2,
Pipe,
SimpleDots,
SimpleDotsScrolling,
Star,
Star2,
Flip,
Hamburger,
GrowVertical,
GrowHorizontal,
Balloon,
Balloon2,
Noise,
Bounce,
BoxBounce,
BoxBounce2,
Triangle,
Arc,
Circle,
SquareCorners,
CircleQuarters,
CircleHalves,
Squish,
Toggle,
Toggle2,
Toggle3,
Toggle4,
Toggle5,
Toggle6,
Toggle7,
Toggle8,
Toggle9,
Toggle10,
Toggle11,
Toggle12,
Toggle13,
Arrow,
Arrow2,
Arrow3,
BouncingBar,
BouncingBall,
Smiley,
Monkey,
Hearts,
Clock,
Earth,
Material,
Moon,
Runner,
Pong,
Shark,
Dqpb,
Weather,
Christmas,
Grenade,
Point,
Layer,
BetaWave,
FingerDance,
FistBump,
SoccerHeader,
Mindblown,
Speaker,
OrangePulse,
BluePulse,
OrangeBluePulse,
TimeTravel,
Aesthetic,
}