use std::path::PathBuf;
use std::time::Duration;
use rich::{ColorSystem, Console};
use rich_ext::theme::extended_theme;
use rich_ext::workflow::{CommandRecord, CommandStatus, CompletionSummary, ManualClock, TaskTree};
struct Shots {
dir: Option<PathBuf>,
}
impl Shots {
fn from_args() -> Self {
let args: Vec<String> = std::env::args().skip(1).collect();
let dir = args
.iter()
.position(|arg| arg == "--svg")
.and_then(|i| args.get(i + 1))
.map(PathBuf::from);
Shots { dir }
}
fn shot(&self, name: &str, width: usize, body: impl FnOnce(&Console)) {
match &self.dir {
None => {
let console = Console::builder().theme(extended_theme()).build();
console.print_str(&format!("[dim]── {name} ──[/]"));
body(&console);
}
Some(dir) => {
let console = Console::builder()
.width(width)
.force_terminal(true)
.color_system(Some(ColorSystem::Truecolor))
.theme(extended_theme())
.build();
let id = format!("guide_workflow-{name}");
let svg = console.export_svg("rich-ext", &id, body);
std::fs::create_dir_all(dir).expect("create the SVG directory");
let path = dir.join(format!("{id}.svg"));
std::fs::write(&path, svg).expect("write the SVG");
eprintln!("wrote {}", path.display());
}
}
}
}
fn ms(millis: u64) -> Duration {
Duration::from_millis(millis)
}
fn run_for_real(console: &Console) {
use rich_ext::cancel::CancelToken;
use rich_ext::workflow::CommandRunner;
use std::process::Command;
let cancel = CancelToken::new(); let record = CommandRunner::new()
.cancel(cancel.clone())
.on_update(|so_far| {
let _ = so_far.lines.len();
})
.run(Command::new("rustc").arg("--version"));
console.print(&record);
}
fn deploy() -> (TaskTree, ManualClock) {
let clock = ManualClock::new(); let mut tree = TaskTree::with_clock(clock.clone()).title("Deploy");
let build = tree.add(None, "build");
let compile = tree.add(Some(build), "compile");
let link = tree.add(Some(build), "link");
let upload = tree.add(None, "upload");
let assets = tree.add(Some(upload), "assets");
let images = tree.add(Some(upload), "images");
tree.add(None, "verify");
tree.start(compile);
clock.advance(ms(9_200));
tree.succeed(compile).start(link);
clock.advance(ms(3_200));
tree.succeed(link).start(assets);
clock.advance(ms(1_100));
tree.warn(assets, "2 files unchanged").start(images);
tree.progress(images, 21, Some(50));
clock.advance(ms(1_900));
(tree, clock)
}
fn main() {
let shots = Shots::from_args();
shots.shot("command", 70, |console| {
let record = CommandRecord::new("cargo", ["build", "--release"])
.stdout(" Compiling rs-rich v0.0.11\n Compiling rs-rich-ext v0.0.10")
.stderr("warning: unused variable: `width`")
.stdout(" Finished `release` profile [optimized] target(s)")
.status(CommandStatus::Exited(0))
.duration(ms(41_300));
console.print(&record.view().tail(3));
});
shots.shot("failure", 70, |console| {
let record = CommandRecord::new("cargo", ["test", "-p", "demo"])
.cwd("crates/demo")
.stdout("running 3 tests\ntest parse::empty ... ok\ntest parse::quoted ... FAILED")
.stderr("error: test failed, to rerun pass `--lib`")
.status(CommandStatus::Exited(101))
.duration(ms(4_250));
console.print(
&record
.view()
.help("rerun one test with `cargo test parse::quoted`"),
);
});
shots.shot("running", 70, |console| {
use rich_ext::a11y::AccessibilityPolicy;
let record = CommandRecord::new("npm", ["install"])
.stdout("added 212 packages")
.duration(ms(3_400)); console.print(&record); console.print(&record.view().policy(&AccessibilityPolicy::reduced_motion()));
});
shots.shot("tree", 70, |console| {
let (tree, _) = deploy();
console.print(&tree);
console.print(&tree.view().collapse_finished(true).animate(false));
});
shots.shot("cancel", 70, |console| {
let (mut tree, _) = deploy();
let upload = tree.roots()[1];
let images = tree.children(upload)[1];
let worker = tree.token(images); tree.cancel(upload);
assert!(worker.is_cancelled());
console.print(&tree.view().collapse_finished(true));
});
shots.shot("summary", 70, |console| {
let (mut tree, clock) = deploy();
let upload = tree.roots()[1];
let images = tree.children(upload)[1];
let verify = tree.roots()[2];
clock.advance(ms(2_600));
tree.fail(images, "403 Forbidden")
.skip(verify, Some("upload failed"));
let summary = CompletionSummary::from(&tree)
.next_step("check the bucket policy")
.next_step("rerun with `deploy --resume`");
console.print(&summary);
});
shots.shot("summary-plain", 70, |console| {
use rich_ext::a11y::SymbolSet;
use rich_ext::workflow::State;
let summary = CompletionSummary::new("Lint")
.count(State::Succeeded, 118)
.count(State::Warning, 2)
.duration(ms(6_800))
.next_step("run `lint --fix`")
.symbols(SymbolSet::Ascii);
console.print(&summary);
});
if shots.dir.is_none() {
run_for_real(&Console::builder().theme(extended_theme()).build());
}
}