novel-cli 0.17.0

A set of tools for downloading novels from the web, manipulating text, and generating EPUB
Documentation
use std::env;
use std::path::PathBuf;

use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Style};
use clap::{Parser, Subcommand, ValueEnum};
use clap_verbosity_flag::Verbosity;
use fluent_templates::Loader;
use supports_color::Stream;
use sysinfo::System;

use crate::cmd::bookshelf::Bookshelf;
use crate::cmd::build::Build;
use crate::cmd::check::Check;
use crate::cmd::completions::Completions;
use crate::cmd::download::Download;
use crate::cmd::epub::Epub;
use crate::cmd::info::Info;
use crate::cmd::read::Read;
use crate::cmd::real_cugan::RealCugan;
use crate::cmd::search::Search;
use crate::cmd::sign::Sign;
use crate::cmd::template::Template;
use crate::cmd::transform::Transform;
use crate::cmd::unzip::Unzip;
use crate::cmd::update::Update;
use crate::cmd::zip::Zip;
use crate::{LANG_ID, LOCALES};

shadow_rs::shadow!(shadow_build);

#[must_use]
#[derive(Parser)]
#[command(author, version = version_msg(), about = about_msg(),
    long_about = None, propagate_version = true, styles = get_styles())]
pub struct Config {
    #[command(subcommand)]
    pub command: Commands,

    #[arg(long, value_enum, global = true,
        help = LOCALES.lookup(&LANG_ID, "backtrace"))]
    pub backtrace: Option<Backtrace>,

    #[arg(long, global = true, conflicts_with = "quiet", default_value_t = false,
        help = LOCALES.lookup(&LANG_ID, "output_log_to_file"))]
    pub output_log_to_file: bool,

    #[command(flatten)]
    pub verbose: Verbosity,
}

#[must_use]
#[derive(Subcommand)]
pub enum Commands {
    Sign(Sign),
    Download(Download),
    Search(Search),
    Info(Info),
    Read(Read),
    Bookshelf(Bookshelf),
    Template(Template),
    Transform(Transform),
    Check(Check),
    Build(Build),
    Epub(Epub),
    Zip(Zip),
    Unzip(Unzip),
    RealCugan(RealCugan),
    Update(Update),
    Completions(Completions),
}

#[must_use]
#[derive(Clone, PartialEq, ValueEnum)]
pub enum Backtrace {
    ON,
    FULL,
}

macro_rules! TELEGRAM {
    () => {
        "https://t.me/TerakomariGandesblood"
    };
}

#[must_use]
const fn about_msg() -> &'static str {
    concat!(
        clap::crate_name!(),
        " ",
        clap::crate_version!(),
        "\nAuthor: ",
        clap::crate_authors!(),
        "\nAuthor's Telegram: ",
        TELEGRAM!(),
        "\nProject home page: ",
        env!("CARGO_PKG_HOMEPAGE"),
    )
}

#[must_use]
fn version_msg() -> String {
    let version = clap::crate_version!();
    let author = clap::crate_authors!();
    let home_page = env!("CARGO_PKG_HOMEPAGE");

    let commit_date = shadow_build::COMMIT_DATE;
    let commit_hash = shadow_build::COMMIT_HASH;
    let build_time = shadow_build::BUILD_TIME;
    let build_target = shadow_build::BUILD_TARGET;

    let os_version = System::long_os_version().unwrap_or(String::from("unknow os"));
    let cpu_arch = System::cpu_arch();

    let current_exe_path = env::current_exe()
        .unwrap_or_else(|_| {
            eprintln!("Unable to get current executable path");
            PathBuf::from(clap::crate_name!())
        })
        .display()
        .to_string();
    let config_dir_path = novel_api::config_dir_path("some-source")
        .unwrap()
        .display()
        .to_string();
    let data_dir_path = novel_api::data_dir_path("some-source")
        .unwrap()
        .display()
        .to_string();

    format!(
        "\
{version}
Author: {author}
Author's Telegram: {}
Project home page: {home_page}

Commit date: {commit_date}
Commit hash: {commit_hash}
Build time: {build_time}
Build target: {build_target}

OS information: {os_version} [{cpu_arch}]

Executable path: {current_exe_path}
Config directory: {config_dir_path}
Data directory: {data_dir_path}",
        TELEGRAM!(),
    )
}

const HEADER: Style = AnsiColor::Green.on_default().bold();
const USAGE: Style = AnsiColor::Green.on_default().bold();
const LITERAL: Style = AnsiColor::Cyan.on_default().bold();
const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
const ERROR: Style = AnsiColor::Red.on_default().bold();
const VALID: Style = AnsiColor::Cyan.on_default().bold();
const INVALID: Style = AnsiColor::Yellow.on_default().bold();
const HELP_STYLES: Styles = Styles::styled()
    .header(HEADER)
    .usage(USAGE)
    .literal(LITERAL)
    .placeholder(PLACEHOLDER)
    .error(ERROR)
    .valid(VALID)
    .invalid(INVALID);

fn get_styles() -> Styles {
    if supports_color::on(Stream::Stdout).is_some() {
        HELP_STYLES
    } else {
        Styles::plain()
    }
}

#[cfg(test)]
mod tests {
    use clap::CommandFactory;

    use super::*;

    #[test]
    fn verify_cli() {
        Config::command().debug_assert()
    }
}