nextjlc/
rename.rs

1/* src/rename.rs */
2
3/* SPDX-License-Identifier: MIT */
4/*
5 * Author Canmi <t@canmi.icu>
6 */
7
8use fancy_regex::Regex;
9use once_cell::sync::Lazy;
10use std::collections::BTreeMap;
11
12/// Defines the supported EDA
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum EdaType {
15    Ad,    // Represents Altium Designer
16    KiCad, // Represents KiCad
17}
18
19/// A struct to hold a single renaming rule.
20/// It pairs a logical name (e.g., "Gerber_TopLayer") with a compiled Regex pattern.
21struct Rule {
22    logical_name: &'static str,
23    pattern: Regex,
24}
25
26/// A helper function to create a Rule, panicking if the regex is invalid.
27/// This ensures that all regex patterns are validated at compile time.
28fn rule(logical_name: &'static str, pattern_str: &'static str) -> Rule {
29    Rule {
30        logical_name,
31        pattern: Regex::new(pattern_str).expect("Invalid regex pattern"),
32    }
33}
34
35/// Static list of rules for Altium Designer, initialized lazily and only once.
36static AD_RULES: Lazy<Vec<Rule>> = Lazy::new(|| {
37    vec![
38        rule("Gerber_BoardOutlineLayer", "(?i)\\.GM(1|13)$"),
39        rule("Gerber_DocumentLayer", "(?i)\\.GM$"),
40        rule("Gerber_TopLayer", "(?i)\\.GTL$"),
41        rule("Gerber_TopSilkscreenLayer", "(?i)\\.GTO$"),
42        rule("Gerber_TopSolderMaskLayer", "(?i)\\.GTS$"),
43        rule("Gerber_TopPasteMaskLayer", "(?i)\\.GTP$"),
44        rule("Gerber_BottomLayer", "(?i)\\.GBL$"),
45        rule("Gerber_BottomSilkscreenLayer", "(?i)\\.GBO$"),
46        rule("Gerber_BottomSolderMaskLayer", "(?i)\\.GBS$"),
47        rule("Gerber_BottomPasteMaskLayer", "(?i)\\.GBP$"),
48        rule("Gerber_InnerLayer1", "(?i)\\.G1$"),
49        rule("Gerber_InnerLayer2", "(?i)\\.G2$"),
50        rule("Gerber_InnerLayer3", "(?i)\\.G3$"),
51        rule("Gerber_InnerLayer4", "(?i)\\.G4$"),
52        rule("Gerber_InnerLayer5", "(?i)\\.G5$"),
53        rule("Gerber_InnerLayer6", "(?i)\\.G6$"),
54        rule("Drill_NPTH_Through", "(?i).*slot\\s?h?oles.*\\.txt$"),
55        rule("Drill_PTH_Through", "(?i).*round\\s?h?oles.*\\.txt$"),
56        rule("Drill_PTH_Through_Via", "(?i)\\.REP$|.*via.*\\.txt$"),
57        rule("Drill_PTH_Through_GBR", "(?i)\\.GD1$"),
58        rule("Drill_PTH_Through_Via_GBR", "(?i)\\.GG1$"),
59        rule("Drill_Report", "(?i)\\.DRR$"),
60        rule("Gerber_Layer_Drawing_Parameters", "(?i)\\.LDP$"),
61    ]
62});
63
64/// Static list of rules for KiCad, initialized lazily and only once.
65static KICAD_RULES: Lazy<Vec<Rule>> = Lazy::new(|| {
66    vec![
67        rule("Gerber_BoardOutlineLayer", "(?i).*Edge_Cuts.*"),
68        rule("Gerber_DocumentLayer", "(?i).*GM.*"),
69        rule("Gerber_TopLayer", "(?i).*F_Cu.*"),
70        rule("Gerber_TopSilkscreenLayer", "(?i).*F_Silkscreen.*"),
71        rule("Gerber_TopSolderMaskLayer", "(?i).*F_Mask.*"),
72        rule("Gerber_TopPasteMaskLayer", "(?i).*F_Paste.*"),
73        rule("Gerber_BottomLayer", "(?i).*B_Cu.*"),
74        rule("Gerber_BottomSilkscreenLayer", "(?i).*B_Silkscreen.*"),
75        rule("Gerber_BottomSolderMaskLayer", "(?i).*B_Mask.*"),
76        rule("Gerber_BottomPasteMaskLayer", "(?i).*B_Paste.*"),
77        rule("Gerber_InnerLayer1", "(?i).*In1_Cu.*"),
78        rule("Gerber_InnerLayer2", "(?i).*In2_Cu.*"),
79        rule("Gerber_InnerLayer3", "(?i).*In3_Cu.*"),
80        rule("Gerber_InnerLayer4", "(?i).*In4_Cu.*"),
81        rule("Gerber_InnerLayer5", "(?i).*In5_Cu.*"),
82        rule("Gerber_InnerLayer6", "(?i).*In6_Cu.*"),
83        // Drill rules are ordered from most to least specific.
84        rule("Drill_PTH_Through_Via", "(?i)^.*\\bVIA\\b.*\\.DRL$"),
85        rule("Drill_NPTH_Through", "(?i)^.*\\bNPTH\\b.*\\.DRL$"),
86        rule("Drill_PTH_Through", "(?i)^(?!.*NPTH).*\\.DRL$"), // Matches .DRL if not NPTH
87        rule("Drill_PTH_Through_Via_GBR", "(?i)^.*\\bVIA\\b.*\\.GBR$"),
88        rule("Drill_NPTH_Through_GBR", "(?i)^.*\\bNPTH\\b.*\\.GBR$"),
89        rule("Drill_PTH_Through_GBR", "(?i)^[^N]*PTH[^N]*\\.GBR$"),
90        // KiCAD match -drl_map.gbr
91        rule("Drill_MAP_GBR", r"(?i).*[-_]drl_map(?:\.GBR)?$"),
92        rule("Gerber_GBR_JOB", "(?i)\\.gbrjob$"),
93    ]
94});
95
96/// Maps a logical file type name to its final, standardized filename.
97fn get_final_filename(logical_name: &str) -> String {
98    match logical_name {
99        "Gerber_TopSolderMaskLayer" => "Gerber_TopSolderMaskLayer.GTS".to_string(),
100        "Gerber_TopSilkscreenLayer" => "Gerber_TopSilkscreenLayer.GTO".to_string(),
101        "Gerber_TopPasteMaskLayer" => "Gerber_TopPasteMaskLayer.GTP".to_string(),
102        "Gerber_TopLayer" => "Gerber_TopLayer.GTL".to_string(),
103        "Gerber_InnerLayer1" => "Gerber_InnerLayer1.G1".to_string(),
104        "Gerber_InnerLayer2" => "Gerber_InnerLayer2.G2".to_string(),
105        "Gerber_InnerLayer3" => "Gerber_InnerLayer3.G3".to_string(),
106        "Gerber_InnerLayer4" => "Gerber_InnerLayer4.G4".to_string(),
107        "Gerber_InnerLayer5" => "Gerber_InnerLayer5.G5".to_string(),
108        "Gerber_InnerLayer6" => "Gerber_InnerLayer6.G6".to_string(),
109        "Gerber_BottomSolderMaskLayer" => "Gerber_BottomSolderMaskLayer.GBS".to_string(),
110        "Gerber_BottomSilkscreenLayer" => "Gerber_BottomSilkscreenLayer.GBO".to_string(),
111        "Gerber_BottomPasteMaskLayer" => "Gerber_BottomPasteMaskLayer.GBP".to_string(),
112        "Gerber_BottomLayer" => "Gerber_BottomLayer.GBL".to_string(),
113        "Gerber_BoardOutlineLayer" => "Gerber_BoardOutlineLayer.GKO".to_string(),
114        "Drill_PTH_Through" => "Drill_PTH_Through.DRL".to_string(),
115        "Drill_PTH_Through_Via" => "Drill_PTH_Through_Via.DRL".to_string(),
116        "Drill_NPTH_Through" => "Drill_NPTH_Through.DRL".to_string(),
117        "Drill_MAP_GBR" => "Drill_MAP_GBR.GBR".to_string(),
118        "Gerber_GBR_JOB" => "Gerber_GBR_JOB.GBRJOB".to_string(),
119        "Drill_Report" => "Drill_MAP_GBR.DRR".to_string(),
120        "Gerber_Layer_Drawing_Parameters" => "Gerber_GBR_JOB.LDP".to_string(),
121
122        // A fallback for any other matched logical names not in the primary list.
123        _ => format!("{}.GBR", logical_name),
124    }
125}
126
127/// The main function of this module. It takes a list of filenames and an EDA type,
128/// and returns a map of original filenames to their proposed new, standardized names.
129pub fn map_filenames(files: &[String], eda_type: EdaType) -> BTreeMap<String, String> {
130    let rules = match eda_type {
131        EdaType::Ad => &AD_RULES,
132        EdaType::KiCad => &KICAD_RULES,
133    };
134
135    let mut rename_map = BTreeMap::new();
136
137    for file in files {
138        let mut new_name = file.clone(); // Default to original name if no match is found.
139        let mut matched = false;
140
141        for rule in rules.iter() {
142            // Handle the Result from is_match
143            if let Ok(true) = rule.pattern.is_match(file) {
144                new_name = get_final_filename(rule.logical_name);
145                matched = true;
146                break; // Stop after the first successful match.
147            }
148        }
149        if !matched {
150            new_name = file.clone();
151        }
152
153        rename_map.insert(file.clone(), new_name);
154    }
155
156    rename_map
157}