1use std::time::{Duration, Instant};
6
7use tern_sdk::{
8 Input, Options, Session, SurfaceOptions, nodes,
9 ui::{self, Bound, Extent, Status, View},
10};
11
12const STEPS: [&str; 8] = [
14 "Resolving dependencies",
15 "Fetching 214 crates",
16 "Compiling serde v1.0.228",
17 "Compiling tern-sdk v0.1.0",
18 "Linking target/debug/demo",
19 "Running 42 tests",
20 "Packaging demo-0.1.0.tar.gz",
21 "Uploading to the registry",
22];
23
24const STEP: Duration = Duration::from_millis(350);
26
27struct Build {
29 log: String,
31 done: usize,
33 took: Option<u64>,
35 status: Status,
37}
38
39fn view(build: &Build) -> View {
41 let fraction = build.done as f64 / STEPS.len() as f64;
42 let running = build.took.is_none();
43 let activity: ui::Node = if running {
44 ui::spinner()
45 .style("dots")
46 .label(STEPS[build.done.min(STEPS.len() - 1)])
47 .into()
48 } else {
49 ui::icon(if build.status == Status::Done {
50 "check"
51 } else {
52 "x"
53 })
54 .tone(if build.status == Status::Done {
55 "success"
56 } else {
57 "error"
58 })
59 .into()
60 };
61 let mut timer = ui::elapsed().age(0);
62 if let Some(took) = build.took {
63 timer = timer.stopped(took);
64 }
65 View::new().main([ui::card()
66 .key("build")
67 .head("Building demo")
68 .status(build.status.clone())
69 .child(
70 ui::row()
71 .key("head")
72 .gap("sm")
73 .children(nodes![activity.key("a"), timer.key("t")]),
74 )
75 .child(
76 ui::ansi(&build.log)
77 .key("log")
78 .follow(true)
79 .max(Bound::h(Extent::Lines(8.0))),
80 )
81 .child(ui::progress().key("bar").value(fraction).label(format!(
82 "{}/{}",
83 build.done,
84 STEPS.len()
85 )))])
86}
87
88fn wait(session: &mut Session, time: Duration) -> Result<bool, tern_sdk::Error> {
90 let until = Instant::now() + time;
91 loop {
92 let left = until.saturating_duration_since(Instant::now());
93 match session.next(Some(left))? {
94 None => return Ok(false),
95 Some(Input::Key(key)) if key.is("ctrl+c") => return Ok(true),
96 Some(_) => {},
97 }
98 }
99}
100
101fn main() -> Result<(), tern_sdk::Error> {
102 let Some(mut session) = Session::connect(Options::new())? else {
103 for (i, step) in STEPS.iter().enumerate() {
104 println!("[{}/{}] {step}", i + 1, STEPS.len());
105 std::thread::sleep(STEP);
106 }
107 println!("Built demo.");
108 return Ok(());
109 };
110 let start = Instant::now();
111 let sf = session.open(SurfaceOptions::flow().title("progress"))?;
112 let mut build =
113 Build { log: String::new(), done: 0, took: None, status: Status::Running };
114 session.render(sf, view(&build))?;
115 for step in STEPS {
116 if wait(&mut session, STEP)? {
117 build.status = Status::Cancelled;
118 break;
119 }
120 build.log.push_str("\x1b[32m✓\x1b[0m ");
121 build.log.push_str(step);
122 build.log.push_str("\r\n");
123 build.done += 1;
124 session.render(sf, view(&build))?;
125 }
126 if build.status == Status::Running {
127 build.status = Status::Done;
128 }
129 let took = start.elapsed();
130 build.took = Some(took.as_millis() as u64);
131 session.render(sf, view(&build))?;
132 session.close()?;
133 match build.status {
134 Status::Done => println!("Built demo in {:.1}s.", took.as_secs_f64()),
135 _ => println!("Build cancelled."),
136 }
137 Ok(())
138}