1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::{fs, path::PathBuf};

use clap::Args;
use color_eyre::eyre::Result;
use fluent_templates::Loader;
use novel_api::Timing;
use regex::Regex;
use tracing::info;

use crate::{
    cmd::Convert,
    utils::{self, UNIX_LINE_BREAK, WINDOWS_LINE_BREAK},
    LANG_ID, LOCALES,
};

#[must_use]
#[derive(Args)]
#[command(arg_required_else_help = true,
    about = LOCALES.lookup(&LANG_ID, "transform_command").unwrap())]
pub struct Transform {
    #[arg(help = LOCALES.lookup(&LANG_ID, "markdown_path").unwrap())]
    pub markdown_path: PathBuf,

    #[arg(short, long, value_enum, value_delimiter = ',',
        help = LOCALES.lookup(&LANG_ID, "converts").unwrap())]
    pub converts: Vec<Convert>,

    #[arg(short, long, default_value_t = false,
        help = LOCALES.lookup(&LANG_ID, "delete").unwrap())]
    pub delete: bool,
}

pub fn execute(config: Transform) -> Result<()> {
    let mut timing = Timing::new();

    utils::ensure_markdown_file(&config.markdown_path)?;

    let input_markdown_file_path = dunce::canonicalize(&config.markdown_path)?;
    let input_dir = input_markdown_file_path.parent().unwrap().to_path_buf();
    let input_file_stem = input_markdown_file_path
        .file_stem()
        .unwrap()
        .to_str()
        .unwrap()
        .to_string();
    info!(
        "Input Markdown file path: `{}`",
        input_markdown_file_path.display()
    );

    let (mut meta_data, markdown) = utils::read_markdown(&input_markdown_file_path)?;

    meta_data.title = utils::convert_str(&meta_data.title, &config.converts)?;
    meta_data.author = utils::convert_str(&meta_data.author, &config.converts)?;
    meta_data.lang = utils::lang(&config.converts);
    if meta_data.description.is_some() {
        let mut description = Vec::new();

        for line in meta_data
            .description
            .as_ref()
            .unwrap()
            .split(UNIX_LINE_BREAK)
        {
            description.push(utils::convert_str(line, &config.converts)?);
        }

        meta_data.description = Some(description.join(UNIX_LINE_BREAK));
    }
    if meta_data.cover_image.is_some() {
        meta_data.cover_image = Some(PathBuf::from(
            utils::convert_image(
                input_dir.join(meta_data.cover_image.unwrap()),
                config.delete,
            )?
            .file_name()
            .unwrap()
            .to_str()
            .unwrap(),
        ));
    }

    let events = utils::to_markdown_events(&markdown, &config.converts, &input_dir, config.delete)?;

    let mut buf = String::with_capacity(4096);
    buf.push_str(format!("---{}", UNIX_LINE_BREAK).as_str());
    buf.push_str(&serde_yaml::to_string(&meta_data)?);
    buf.push_str(format!("...{0}{0}", UNIX_LINE_BREAK).as_str());

    let mut markdown_buf = String::with_capacity(markdown.len());
    pulldown_cmark_to_cmark::cmark(events.iter(), &mut markdown_buf)?;

    let regex = Regex::new(&format!("({})+", UNIX_LINE_BREAK))?;
    buf.push_str(&regex.replace_all(&markdown_buf, format!("{0}{0}", UNIX_LINE_BREAK)));

    while buf.ends_with(UNIX_LINE_BREAK) {
        buf.pop();
    }
    buf.push_str(UNIX_LINE_BREAK);

    if config.delete {
        utils::remove_file_or_dir(input_markdown_file_path)?;
    } else {
        let backup_markdown_file_path = input_dir.join(format!("{input_file_stem}.old.md"));
        info!(
            "Backup Markdown file path: `{}`",
            backup_markdown_file_path.display()
        );

        fs::rename(&input_markdown_file_path, backup_markdown_file_path)?;
    }

    let new_file_name =
        utils::to_markdown_file_name(utils::convert_str(&meta_data.title, &config.converts)?);
    let output_markdown_file_path = input_dir.join(new_file_name);
    info!(
        "Output Markdown file path: `{}`",
        output_markdown_file_path.display()
    );

    if cfg!(windows) {
        buf = buf.replace(UNIX_LINE_BREAK, WINDOWS_LINE_BREAK);
    }
    fs::write(output_markdown_file_path, buf)?;

    info!("Time spent on `transform`: {}", timing.elapsed()?);

    Ok(())
}