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
use crate::ClipboardError;
use std::path::Path;
use tokio::fs as async_fs;
use tracing::warn;

pub fn normalize_path(path: &str) -> String {
    let path = Path::new(path);
    let normalized_path = if path.is_relative() && path.starts_with("./") {
        path.strip_prefix("./").unwrap().to_owned()
    } else {
        path.to_owned()
    };

    normalized_path.to_string_lossy().replace("\\", "/")
}

pub fn expand_patterns(patterns: &[String]) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let mut files = Vec::new();
    for pattern in patterns {
        let normalized_pattern = normalize_path(pattern);
        let path = Path::new(&normalized_pattern);

        if path.is_dir() {
            for entry in walkdir::WalkDir::new(path)
                .into_iter()
                .filter_map(|e| e.ok())
            {
                if entry.file_type().is_file() {
                    files.push(entry.path().to_string_lossy().to_string());
                }
            }
        } else {
            for entry in glob::glob(&normalized_pattern)? {
                match entry {
                    Ok(path) if path.is_file() => files.push(path.to_string_lossy().to_string()),
                    Err(e) => warn!("Error processing pattern {}: {:?}", pattern, e),
                    _ => {}
                }
            }
        }
    }
    Ok(files)
}

pub async fn read_file_content(file_path: &str) -> Result<String, ClipboardError> {
    async_fs::read_to_string(file_path)
        .await
        .map_err(|err| ClipboardError::FileReadError(err.to_string()))
}

pub fn format_content(
    content: &str,
    file: &str,
    no_markdown: bool,
    line_number: Option<usize>,
    prefix: &str,
    filename_format: String,
) -> Result<String, ClipboardError> {
    let mut formatted_content = String::new();
    let file = normalize_path(file);
    let ext = file.split('.').last().unwrap_or("");

    match filename_format.as_str() {
        "None" => {}
        "MarkdownFirstCodeLine" => {
            if !no_markdown {
                formatted_content.push_str(&format!("```{}\n", ext));
            }
            formatted_content.push_str(&get_filename_comment(ext, &file));
        }
        "MarkdownHeading" => {
            formatted_content.push_str(&format!("### `{}`\n", file));
            if !no_markdown {
                formatted_content.push_str(&format!("```{}\n", ext));
            }
        }
        _ => {
            if !no_markdown {
                formatted_content.push_str(&format!("```{}\n", ext));
            }
        }
    }

    formatted_content.push_str(&get_line_numbered_content(content, line_number, prefix));

    if !no_markdown {
        formatted_content.push_str("```\n");
    }

    Ok(formatted_content)
}

fn get_filename_comment(ext: &str, filename: &str) -> String {
    match ext {
        "rs" | "js" | "ts" | "tsx" | "java" | "c" | "cpp" | "h" | "cs" | "fs" | "json" => {
            format!("// filename: {}\n", filename)
        }
        "py" | "toml" | "sh" | "yml" | "yaml" => format!("# filename: {}\n", filename),
        "html" | "xml" => format!("<!-- filename: {} -->\n", filename),
        "css" => format!("/* filename: {} */\n", filename),
        _ => format!("// filename: {}\n", filename),
    }
}

fn get_line_numbered_content(content: &str, line_number: Option<usize>, prefix: &str) -> String {
    let mut numbered_content = String::new();
    for (i, line) in content.lines().enumerate() {
        if let Some(digits) = line_number {
            numbered_content.push_str(&format!("{:0width$}{}", i + 1, prefix, width = digits));
        }
        numbered_content.push_str(line);
        numbered_content.push('\n');
    }
    numbered_content
}