Skip to main content

j_cli/command/
time.rs

1use crate::constants::time_function;
2use crate::{error, info, usage};
3use indicatif::{ProgressBar, ProgressStyle};
4use std::io::{self, Write};
5
6/// 处理 time 命令: j time countdown <duration>
7/// duration 支持: 30s(秒)、5m(分钟)、1h(小时),不带单位默认为分钟
8pub fn handle_time(function: &str, arg: &str) {
9    if function != time_function::COUNTDOWN {
10        error!("❌ 未知的功能: {},目前仅支持 countdown", function);
11        usage!("j time countdown <duration>");
12        info!(
13            "  duration 格式: 30s(秒), 5m(分钟), 1h(小时), 不带单位默认为分钟"
14        );
15        return;
16    }
17
18    let duration_secs = parse_duration(arg);
19    if duration_secs <= 0 {
20        error!("❌ 无效的时长: {}", arg);
21        return;
22    }
23
24    info!("⏳ 倒计时开始:{}", format_duration_display(duration_secs as u64));
25    run_countdown(duration_secs as u64);
26}
27
28/// 格式化时长为可读的中文显示
29fn format_duration_display(secs: u64) -> String {
30    if secs >= 3600 {
31        let h = secs / 3600;
32        let m = (secs % 3600) / 60;
33        if m > 0 {
34            format!("{}小时{}分钟", h, m)
35        } else {
36            format!("{}小时", h)
37        }
38    } else if secs >= 60 {
39        let m = secs / 60;
40        let s = secs % 60;
41        if s > 0 {
42            format!("{}分{}秒", m, s)
43        } else {
44            format!("{}分钟", m)
45        }
46    } else {
47        format!("{}秒", secs)
48    }
49}
50
51/// 解析时长字符串为秒数
52fn parse_duration(s: &str) -> i64 {
53    let s = s.trim();
54    if s.ends_with('s') {
55        s[..s.len() - 1].parse::<i64>().unwrap_or(-1)
56    } else if s.ends_with('m') {
57        s[..s.len() - 1]
58            .parse::<i64>()
59            .map(|m| m * 60)
60            .unwrap_or(-1)
61    } else if s.ends_with('h') {
62        s[..s.len() - 1]
63            .parse::<i64>()
64            .map(|h| h * 3600)
65            .unwrap_or(-1)
66    } else {
67        // 默认单位为分钟
68        s.parse::<i64>().map(|m| m * 60).unwrap_or(-1)
69    }
70}
71
72/// 格式化剩余时间为 HH:MM:SS 或 MM:SS
73fn format_remaining(secs: u64) -> String {
74    if secs >= 3600 {
75        format!("{:02}:{:02}:{:02}", secs / 3600, (secs % 3600) / 60, secs % 60)
76    } else {
77        format!("{:02}:{:02}", secs / 60, secs % 60)
78    }
79}
80
81/// 运行倒计时(带进度条和动画)
82fn run_countdown(total_secs: u64) {
83    let pb = ProgressBar::new(total_secs);
84
85    // 设置进度条样式
86    pb.set_style(
87        ProgressStyle::default_bar()
88            .template("  {spinner:.cyan} ⏱️  {msg}  {wide_bar:.cyan/dark_gray}  {percent}%")
89            .unwrap()
90            .progress_chars("━╸─")
91    );
92
93    pb.set_message(format_remaining(total_secs));
94
95    let start = std::time::Instant::now();
96
97    for elapsed in 1..=total_secs {
98        // 精确校准每秒
99        let next_tick = start + std::time::Duration::from_secs(elapsed);
100        let now = std::time::Instant::now();
101        if next_tick > now {
102            std::thread::sleep(next_tick - now);
103        }
104
105        let remaining = total_secs - elapsed;
106        pb.set_position(elapsed);
107        pb.set_message(format_remaining(remaining));
108    }
109
110    pb.finish_and_clear();
111
112    println!("  🎉 Time's up! 倒计时结束!");
113    println!();
114
115    // 结束动画
116    display_celebration();
117}
118
119/// 结束庆祝动画
120fn display_celebration() {
121    let frames = [
122        "  🔔 Ding Ding! Time's Up! 🔔",
123        "  💢😤💢 Stop! Stop! Stop! 💢😤💢",
124        "  🔥😠🔥 How dare you don't stop! 🔥😠🔥",
125    ];
126
127    // 先播放系统提示音(macOS,非阻塞)
128    #[cfg(target_os = "macos")]
129    {
130        let _ = std::process::Command::new("afplay")
131            .arg("/System/Library/Sounds/Glass.aiff")
132            .spawn();
133    }
134
135    for i in 0..6 {
136        // 用空格覆盖上一帧的残余字符
137        print!("\r{:<60}", frames[i % frames.len()]);
138        let _ = io::stdout().flush();
139        std::thread::sleep(std::time::Duration::from_millis(600));
140    }
141    println!();
142}