novel_cli/
lib.rs

1pub mod cmd;
2pub mod config;
3pub mod renderer;
4pub mod utils;
5
6use std::io::{self, Stdout};
7use std::sync::LazyLock;
8use std::{env, panic};
9
10use color_eyre::config::HookBuilder;
11use color_eyre::eyre::{self, Result};
12use ratatui::Terminal;
13use ratatui::backend::CrosstermBackend;
14use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
15use ratatui::crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
16use ratatui::crossterm::{self};
17use unic_langid::LanguageIdentifier;
18
19fluent_templates::static_loader! {
20    static LOCALES = {
21        locales: "./locales",
22        fallback_language: "en-US",
23        // Windows terminal does not seem to support isolating marks
24        // See https://github.com/XAMPPRocky/fluent-templates/issues/21
25        customise: |bundle| bundle.set_use_isolating(false),
26    };
27}
28
29pub static LANG_ID: LazyLock<LanguageIdentifier> = LazyLock::new(|| {
30    let mut locale = sys_locale::get_locale().unwrap_or_else(|| {
31        eprintln!("Failed to get active locale for the system, use `en-US`");
32        String::from("en-US")
33    });
34
35    if locale == "zh-CN" {
36        locale = "zh-Hans".to_string();
37    } else if locale == "zh-HK" || locale == "zh-TW" {
38        locale = "zh-Hant".to_string();
39    } else if locale == "C" {
40        locale = "en-US".to_string();
41    }
42
43    match locale.parse::<LanguageIdentifier>() {
44        Ok(lang_id) => lang_id,
45        Err(error) => {
46            eprintln!("Failed to parse LanguageIdentifier: {error}, use `en-US`");
47            "en-US".parse::<LanguageIdentifier>().unwrap()
48        }
49    }
50});
51
52pub fn init_error_hooks(restore: bool) -> Result<()> {
53    if env::var("RUST_SPANTRACE").is_err() {
54        unsafe {
55            env::set_var("RUST_SPANTRACE", "0");
56        }
57    }
58
59    let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks();
60
61    let panic_hook = panic_hook.into_panic_hook();
62    panic::set_hook(Box::new(move |panic_info| {
63        if restore {
64            let _ = restore_terminal();
65        }
66        panic_hook(panic_info);
67    }));
68
69    let eyre_hook = eyre_hook.into_eyre_hook();
70    eyre::set_hook(Box::new(move |e| {
71        if restore {
72            let _ = restore_terminal();
73        }
74        eyre_hook(e)
75    }))?;
76
77    Ok(())
78}
79
80pub(crate) type Tui = Terminal<CrosstermBackend<Stdout>>;
81
82pub(crate) fn init_terminal() -> Result<Tui> {
83    crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
84    terminal::enable_raw_mode()?;
85    Ok(Terminal::new(CrosstermBackend::new(io::stdout()))?)
86}
87
88pub(crate) fn restore_terminal() -> Result<()> {
89    crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
90    terminal::disable_raw_mode()?;
91    Ok(())
92}