novel_cli/
lib.rs

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