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