use std::ops::Range;
use std::time::Duration;
use conciliator::{
Claw,
Conciliator,
Buffer,
edit::Edited,
Inline,
Input,
inline,
LambdaFmt,
List,
Paint,
term::COLOR_CODES,
Tag,
Tree,
Wrap,
WrapBold
};
use conciliator::style::{
Color,
set_palette,
get_palette
};
use serde::{
Serialize,
Deserialize
};
fn main() {
while menu() {}
}
macro_rules! maybe_async {
($b:expr) => {{
#[cfg(feature = "tokio")]
let _output = {
let async_block = async { $b };
tokio::runtime::Runtime::new().unwrap().block_on(async_block)
};
#[cfg(not(feature = "tokio"))]
let _output = { $b };
#[allow(unreachable_code)]
_output
}};
}
fn menu() -> bool {
let mut con = conciliator::init();
con.status("Welcome to the Conciliator example!");
let menu = vec![
WrapBold::Beta("Status Messages"),
WrapBold::Beta("Printing Colors"),
WrapBold::Beta("Editing Text"),
WrapBold::Beta("Select"),
WrapBold::Beta("Printing Lists and Trees"),
WrapBold::Beta("Change Color Palette"),
WrapBold::Beta("Spinner Animations"),
WrapBold::Beta("Spinput"),
WrapBold::Delta("Quit")
];
let selection = maybe_async!({
let mut menu = menu;
let sp = con.spin("Welcome to the Conciliator example!");
let select = conciliator::input::Select::new(
List::indexed(menu.iter()).with_count("options"),
"Select a feature to try out or quit"
);
#[cfg(not(feature = "tokio"))]
let selection = menu.swap_remove(sp.input(select));
#[cfg(feature = "tokio")]
let selection = menu.swap_remove(sp.input(select).await);
#[cfg(not(feature = "tokio"))]
sp.clear();
#[cfg(feature = "tokio")]
sp.clear().await;
selection
});
match selection.inner() {
"Status Messages" => status_test(&con),
"Printing Colors" => color_test(&con),
"Editing Text" => edit_test(&con),
"Select" => select_test(&con),
"Printing Lists and Trees" => print_test(&con),
"Change Color Palette" => change_palette(&con),
"Spinner Animations" => pick_spinner(&mut con),
"Spinput" => spinput(&mut con),
"Quit" => false,
_ => unreachable!()
}
}
fn status_test(con: &Claw) -> bool {
con.status("Printing example messages");
con.line(..);
con.status("General status message");
con.info("Supplementary info message :^)");
con.warn("An error is imminent!");
con.error("Error occurred, message printed!");
con.line(..);
con.status("Done printing example messages");
con.info("Info and status messages are very similar semantically");
con.info("Apply them judiciously to highlight and add some contrast!");
con.confirm(true, "Return to menu?")
}
fn color_test(con: &Claw) -> bool {
let symbols = ['▲', '⬢', '▼'];
for symbol in symbols {
let mut rainbow = con.line("‹«‹~×~›»› ");
for color in COLOR_CODES {
rainbow.push_with_any_color_bold(color, &symbol);
rainbow.push_plain(" ");
}
rainbow.push_plain("‹«‹~×~›»›");
}
con.confirm(true, "Return to menu?")
}
fn select_test(con: &Claw) -> bool {
let uuids = vec![
Wrap::Iota("62bfe952-c9b3-4d32-a2fb-eba5a9b77136"),
Wrap::Iota("3ba08f2d-6b37-42d6-a12b-b22e0b5ad01f"),
Wrap::Iota("0730fbdf-9703-4ffb-9186-d93e412cec63"),
Wrap::Iota("36f295d9-1a94-42f4-a4f2-f2c298cf7716"),
Wrap::Iota("879738e8-82e5-418f-8557-d7b95c6be06a"),
Wrap::Iota("2d5f0e37-f578-4764-9ffe-9542c99ef761")
];
let uuid = con.select(uuids, "UUIDs", "Select UUID").unwrap();
con.info("Selected ")
.push(&uuid)
.push_plain("!");
con.confirm(true, "Return to menu?")
}
fn print_test(con: &Claw) -> bool {
con.info("Printing example list");
print_env_vars(con);
con.line(..);
con.info("Trees:");
mini_tree(con);
con.confirm(true, "Return to menu?")
}
fn print_env_vars<C: Conciliator>(con: &C) {
struct EnvVar((String, String));
impl Inline for EnvVar {
fn inline(&self, buffer: &mut Buffer) {
buffer
.push_beta_bold(&self.0.0)
.push(": ")
.push(&self.0.1);
}
}
List::headless(std::env::vars())
.with_header("Environment variables")
.with_wrap(EnvVar)
.print_to(con);
}
fn mini_tree<C: Conciliator>(con: &C) {
struct Animal (&'static str, Vec<Animal>);
let tree = [
Animal ("Reptiles", vec![
Animal ("Frogs", vec![]),
Animal ("Lizards", vec![
Animal ("Chameleon", vec![]),
Animal ("Gecko", vec![]),
])
]),
Animal ("Mammals", vec![
Animal ("Tiger", vec![]),
Animal ("Monkey", vec![])
]),
Animal ("Crab", vec![])
];
impl Inline for Animal {
fn inline(&self, buffer: &mut Buffer) {
buffer.push_plain(self.0);
}
}
con.print(Tree::new(
"Animals:",
tree.iter(),
|a| Some(a.1.iter())
));
}
fn edit_test(con: &Claw) -> bool {
#[derive(Serialize, Deserialize)]
struct Entry {
id: usize,
name: String
}
impl Inline for Entry {
fn inline(&self, buffer: &mut Buffer) {
buffer.push_bold("[")
.push_alpha_bold(&self.id)
.push_bold(" / ")
.push_beta_bold(&self.name)
.push_bold("]");
}
}
conciliator::edit_as_toml!(Entry);
let edit_result = con.edit(Entry{id: 33, name: "Test123".to_owned()});
match edit_result {
Edited::Ok(s) => {con.status("Edited entry:\n").push(&s);},
Edited::Cancelled => {con.info("Edit aborted!");},
Edited::Err(e) => {con.error(..).push_plain(&e);}
};
con.confirm(true, "Return to menu?")
}
fn print_available_colors(con: &Claw) {
con.status("Available colors:");
let iters = [
#[allow(clippy::iter_skip_zero)]
COLOR_CODES.iter().enumerate().skip(0).step_by(2),
COLOR_CODES.iter().enumerate().skip(1).step_by(2)
];
for mut iter in iters {
let mut line = con.line(" ");
let (i, &color) = iter.next().unwrap();
line
.push(format_args!("{i} "))
.push_with_any_color(color, "███");
for (i, &color) in iter {
line
.push_bold(" │ ")
.push(format_args!("{i} "))
.push_with_any_color(color, "███");
}
}
}
fn change_palette(con: &Claw) -> bool {
con.status("Current color palette:");
con.print(get_palette());
struct RangeInput<P>(P, Range<usize>);
impl<P: Inline> Input for RangeInput<P> {
type T = usize;
fn prompt(&self, buffer: &mut Buffer) {
buffer.push(&self.0)
.push(" [")
.push(self.1.start)
.push(" - ")
.push(self.1.end - 1)
.push("]: ");
}
fn validate(&self, user_input: &str) -> Option<usize> {
user_input
.parse()
.ok()
.filter(|i: &usize| self.1.contains(i))
}
}
let mut new_palette = get_palette();
let colors = [
(Color::Alpha, &mut new_palette.alpha),
(Color::Beta, &mut new_palette.beta),
(Color::Gamma, &mut new_palette.gamma),
(Color::Delta, &mut new_palette.delta),
(Color::Zeta, &mut new_palette.zeta),
(Color::Iota, &mut new_palette.iota),
(Color::Omega, &mut new_palette.omega)
];
print_available_colors(con);
let prompt = inline!(
"Pick a new color for the ",
Tag(Color::Omega, "…"),
"brackets"
);
let idx = con.input(RangeInput(prompt, 0..16));
let new_code = COLOR_CODES[idx];
new_palette.tag = new_code;
for (color, color_ref) in colors {
let prompt = LambdaFmt(|b| {b
.push("Pick a new color as ")
.push_with_color_bold(color, &format_args!("{color:?}"));
});
let idx = con.input(RangeInput(prompt, 0..16));
let new_code = COLOR_CODES[idx];
*color_ref = new_code;
}
con.status("New color palette:");
con.print(new_palette);
let env_strings = [
format!("\"{new_palette:?}\""),
format!("\"{new_palette:#?}\"")
];
con.info("To use this palette, set CONCILIATOR_PALETTE at compile-time");
con.info("You can use either of these strings:");
con.print(List::headless(env_strings.iter()));
if con.confirm(true, "Apply this color palette now?") {
unsafe {set_palette(new_palette);}
con.status("Applied!");
}
true
}
macro_rules! sleep {
($t:expr) => {
#[cfg(feature = "tokio")]
tokio::time::sleep($t).await;
#[cfg(not(feature = "tokio"))]
std::thread::sleep($t);
};
}
fn pick_spinner(con: &mut Claw) -> bool {
use conciliator::spin::*;
struct SpinWith(&'static str, Animation);
impl Inline for SpinWith {
fn inline(&self, buffer: &mut Buffer) {
WrapBold::Beta(self.0).inline(buffer);
}
}
const POINT_SIX: Duration = Duration::from_millis(600);
const ONE: Duration = Duration::from_secs(1);
const THREE: Duration = Duration::from_secs(3);
maybe_async!(loop {
let spin_menu = vec![
SpinWith("Chase", CHASE),
SpinWith("Dot", DOT),
SpinWith("Loop", LOOP),
SpinWith("Wide", LOOP_WIDE),
SpinWith("Square", SQUARE),
SpinWith("Triangle", TRIANGLE)
];
let spinner = con.select(
spin_menu,
"spinners",
"Pick a spinner to try out"
).unwrap().1;
let sp = Spinner::new(
con,
spinner,
"Spinning"
);
sleep!(POINT_SIX);
sp.message("Spinning.");
sleep!(POINT_SIX);
sp.message("Spinning..");
sleep!(POINT_SIX);
sp.message("Spinning...");
sleep!(THREE);
sp.info("Regular output is still possible even while spinning");
sleep!(THREE);
sp.message("Printing example messages");
sleep!(ONE);
sp.status("General status message");
sp.info("Supplementary info message :^)");
sp.warn("An error is imminent!");
sp.error("Error occurred, message printed!");
sleep!(ONE);
sp.info("Info and status messages are very similar semantically");
sp.info("Apply them judiciously to highlight and add some contrast!");
sp.message("Done printing example messages");
sleep!(ONE);
sp.message("Printing small example tree");
sleep!(ONE);
mini_tree(&sp);
sleep!(ONE);
sp.message("Printing ")
.push_alpha("environment")
.push(" variables");
sleep!(ONE);
print_env_vars(&sp);
sleep!(ONE);
sp.message("Done spinning");
#[cfg(feature = "tokio")]
sp.finish().await;
#[cfg(not(feature = "tokio"))]
sp.finish();
if !con.confirm(true, "Try another spinner?") {break}
});
con.confirm(true, "Return to menu?")
}
fn spinput(con: &mut Claw) -> bool {
use conciliator::spin::*;
use conciliator::input::AbortRetryContinue;
let animations = [
("Chase", CHASE),
("Dot", DOT),
("Loop", LOOP),
("Wide", LOOP_WIDE),
("Square", SQUARE),
("Triangle", TRIANGLE)
];
maybe_async!(loop {
for (name, animation) in animations {
let sp = Spinner::new(
con,
animation,
inline!("Spinning with ", WrapBold::Beta(name))
);
#[cfg(feature = "tokio")]
let input = sp.input(AbortRetryContinue::Continue).await;
#[cfg(not(feature = "tokio"))]
let input = sp.input(AbortRetryContinue::Continue);
sp.message("Done!");
#[cfg(feature = "tokio")]
sp.finish().await;
#[cfg(not(feature = "tokio"))]
sp.finish();
match input {
AbortRetryContinue::Abort => return false,
AbortRetryContinue::Retry => break,
AbortRetryContinue::Continue => continue
}
}
});
false
}