From 7570c13337f07ab0f58ce88122f5c42a93d39543 Mon Sep 17 00:00:00 2001 From: apogeeoak <59737221+apogeeoak@users.noreply.github.com> Date: Sat, 18 Mar 2023 13:00:54 -0400 Subject: [PATCH] feat(lib): add lib --- .github/workflows/rust.yaml | 71 ++++++++++++ Cargo.lock | 212 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++ collection.toml | 61 +++++++++++ src/config/collection.rs | 9 ++ src/config/mod.rs | 26 +++++ src/lib.rs | 48 ++++++++ src/library/format.rs | 43 ++++++++ src/library/mod.rs | 1 + src/main.rs | 8 ++ 10 files changed, 488 insertions(+) create mode 100644 .github/workflows/rust.yaml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 collection.toml create mode 100644 src/config/collection.rs create mode 100644 src/config/mod.rs create mode 100644 src/lib.rs create mode 100644 src/library/format.rs create mode 100644 src/library/mod.rs create mode 100644 src/main.rs diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml new file mode 100644 index 0000000..d20d81c --- /dev/null +++ b/.github/workflows/rust.yaml @@ -0,0 +1,71 @@ +name: Rust + +on: + push: + branches: [main] + tags: 'v*' + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + # Fail on all warnings. + RUSTFLAGS: '-Dwarnings' + +jobs: + build: + name: Build ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Toolchain versions + run: | + rustc --version + cargo --version + + - name: Build + run: cargo build --release --all-targets --all-features --verbose + + - name: Clippy + run: cargo clippy --release --all-targets --all-features --verbose + + - name: Test + run: cargo test --release --all-targets --all-features + + # Create release only for tag commit. + ## General + - name: General release artifacts + if: ${{ startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' }} + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: collection.toml + + ## Ubuntu + - name: Prepare Ubuntu release + if: ${{ startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' }} + run: strip target/release/collection + + - name: Create Ubuntu release + if: ${{ startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' }} + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: target/release/collection + + ## Windows + - name: Create Windows release + if: ${{ startsWith(github.ref, 'refs/tags') && matrix.os == 'windows-latest' }} + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: target/release/collection.exe diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a587df2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,212 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "collection" +version = "0.1.0" +dependencies = [ + "rand", + "serde", + "toml", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "serde" +version = "1.0.154" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.154" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.7.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7082a95d48029677a28f181e5f6422d0c8339ad8396a39d3f33d62a90c1f6c30" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winnow" +version = "0.3.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..af07341 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "collection" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8" +serde = {version = "1.0", features = ["derive"]} +toml = "0.7" diff --git a/collection.toml b/collection.toml new file mode 100644 index 0000000..d3bd1ba --- /dev/null +++ b/collection.toml @@ -0,0 +1,61 @@ +title = "Title" + +[[collections]] +label = "Items" +width = 10 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 0 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 1 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 2 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 3 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 4 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 5 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 6 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 7 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 8 +items = ["One", "Two", "Three", "Four", "Five", "Six", "12345678901234567890", "A", "Be"] + +[[collections]] +label = "Items" +width = 10 +items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"] + +[[collections]] +label = "Items" +width = 10 +items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven"] diff --git a/src/config/collection.rs b/src/config/collection.rs new file mode 100644 index 0000000..495c241 --- /dev/null +++ b/src/config/collection.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; + +// Struct to hold the [[collections]] table. +#[derive(Debug, Deserialize)] +pub struct Collection { + pub label: String, + pub width: u8, + pub items: Vec, +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..50d1629 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,26 @@ +pub mod collection; + +use serde::Deserialize; +use std::error::Error; +use std::fs; + +use collection::Collection; + +// Top level struct to hold entire input configuration. +#[derive(Debug, Deserialize)] +pub struct Config { + pub title: String, + pub collections: Vec, +} + +impl Config { + pub fn read() -> Result> { + let filename = "collection.toml"; + + let contents = fs::read_to_string(filename).map_err(|err| format!("Unable to read file '{}'. Error: {}", filename, err))?; + + let input: Config = toml::from_str(&contents).map_err(|err| format!("Unable to load data from '{}'. Error: {}", filename, err))?; + + Ok(input) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..df9add5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,48 @@ +use crate::{config::Config, library::format::Row}; +use rand::prelude::*; +use std::error::Error; + +mod config; +mod library; + +pub fn main() -> Result<(), Box> { + // Read configuration. + let mut config = Config::read()?; + + // Print title. + println!("\n{}\n", config.title); + + shuffle_items(&mut config); + + print_labels(&config); + print_items(&config); + + Ok(()) +} + +fn shuffle_items(config: &mut Config) { + // Shuffle items in place. + let mut rng = rand::thread_rng(); + for collection in config.collections.iter_mut() { + collection.items.shuffle(&mut rng); + } +} + +fn print_labels(config: &Config) { + let labels = config.collections.iter().map(|c| Row { output: &c.label, width: c.width.into() }); + let widths = config.collections.iter().map(|c| c.width.into()); + println!("{}", library::format::row(labels)); + println!("{}", library::format::divider(widths, None)); +} + +fn print_items(config: &Config) { + let string_empty = String::new(); + let length = config.collections.iter().map(|c| c.items.len()).max().unwrap_or(0); + for index in 0..length { + let items = config.collections.iter().map(|c| Row { + output: c.items.get(index).unwrap_or(&string_empty), + width: c.width.into(), + }); + println!("{}", library::format::row(items)); + } +} diff --git a/src/library/format.rs b/src/library/format.rs new file mode 100644 index 0000000..f63adba --- /dev/null +++ b/src/library/format.rs @@ -0,0 +1,43 @@ +use std::cmp; +use std::fmt::Display; +use std::fmt::Write; + +pub fn cell(item: impl Display, width: usize) -> String { + let item = item.to_string(); + let count = item.chars().take(width + 1).count(); + + match count { + len if len <= width => format!("{:1$}", item, width), + _ if width <= 3 => item.chars().take(width).collect::(), + _ => { + let ellipses = cmp::min(3, width - 3); + let ellipsis = ".".repeat(ellipses); + + format!("{}{}", item.chars().take(width - ellipses).collect::(), ellipsis) + } + } +} + +pub struct Row { + pub output: T, + pub width: usize, +} +pub fn row(items: impl IntoIterator>) -> String { + let mut builder = String::new(); + let mut items = items.into_iter(); + if let Some(item) = items.next() { + builder.push_str(&cell(item.output, item.width)); + } + for item in items { + write!(&mut builder, " | {}", &cell(item.output, item.width)).unwrap(); + } + builder +} + +pub fn divider(widths: impl IntoIterator, divider: Option<&str>) -> String { + // Set the default divider string. + let divider = divider.unwrap_or("-"); + + let size = widths.into_iter().reduce(|acc, item| acc + item + 3).unwrap_or(0); + divider.repeat(size) +} diff --git a/src/library/mod.rs b/src/library/mod.rs new file mode 100644 index 0000000..db7b59d --- /dev/null +++ b/src/library/mod.rs @@ -0,0 +1 @@ +pub mod format; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4229b83 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,8 @@ +use std::process; + +fn main() { + if let Err(err) = collection::main() { + eprintln!("{}", err); + process::exit(1); + } +}