use clap::Parser;
use std::path::PathBuf;
use crate::error::{AppError, Result};
#[derive(Parser, Debug)]
#[command(name = "nlbn")]
#[command(version = "1.0.12")]
#[command(about = "Fast EasyEDA/LCSC to KiCad converter with parallel downloads", long_about = None)]
pub struct Cli {
#[arg(long, value_name = "ID", conflicts_with_all = ["batch", "remove"])]
pub lcsc_id: Option<String>,
#[arg(long, value_name = "FILE", conflicts_with_all = ["lcsc_id", "remove"])]
pub batch: Option<PathBuf>,
#[arg(long, value_name = "ID", conflicts_with_all = ["lcsc_id", "batch"])]
pub remove: Option<String>,
#[arg(long, value_name = "DIR", requires = "remove")]
pub from: Option<PathBuf>,
#[arg(long)]
pub symbol: bool,
#[arg(long)]
pub footprint: bool,
#[arg(long = "3d")]
pub model_3d: bool,
#[arg(long)]
pub full: bool,
#[arg(short, long, default_value = ".")]
pub output: PathBuf,
#[arg(long)]
pub overwrite: bool,
#[arg(long)]
pub v5: bool,
#[arg(long)]
pub project_relative: bool,
#[arg(long)]
pub debug: bool,
#[arg(long)]
pub continue_on_error: bool,
#[arg(long, default_value = "4")]
pub parallel: usize,
}
impl Cli {
pub fn validate(&self) -> Result<()> {
if self.remove.is_some() {
if self.from.is_none() {
return Err(AppError::Other(
"--from directory must be specified when using --remove".to_string()
));
}
if let Some(ref id) = self.remove {
if !id.starts_with('C') || id.len() < 2 {
return Err(AppError::Easyeda(
crate::error::EasyedaError::InvalidLcscId(id.clone())
));
}
}
return Ok(());
}
if self.lcsc_id.is_none() && self.batch.is_none() {
return Err(AppError::Other(
"Either --lcsc-id, --batch, or --remove must be specified".to_string()
));
}
if let Some(ref id) = self.lcsc_id {
if !id.starts_with('C') || id.len() < 2 {
return Err(AppError::Easyeda(
crate::error::EasyedaError::InvalidLcscId(id.clone())
));
}
}
if !self.symbol && !self.footprint && !self.model_3d && !self.full {
return Err(AppError::Other(
"At least one conversion option must be specified (--symbol, --footprint, --3d, or --full)".to_string()
));
}
Ok(())
}
pub fn get_lcsc_ids(&self) -> Result<Vec<String>> {
if let Some(ref id) = self.lcsc_id {
Ok(vec![id.clone()])
} else if let Some(ref batch_file) = self.batch {
use std::fs;
use regex::Regex;
let content = fs::read_to_string(batch_file)
.map_err(|e| AppError::Other(format!("Failed to open batch file: {}", e)))?;
let re = Regex::new(r"C\d+").unwrap();
let ids: Vec<String> = re.find_iter(&content)
.map(|m| m.as_str().to_string())
.collect();
if ids.is_empty() {
return Err(AppError::Other("No valid LCSC IDs found in batch file".to_string()));
}
log::info!("Loaded {} LCSC IDs from batch file", ids.len());
Ok(ids)
} else {
Err(AppError::Other("No LCSC ID source specified".to_string()))
}
}
pub fn kicad_version(&self) -> KicadVersion {
if self.v5 {
KicadVersion::V5
} else {
KicadVersion::V6
}
}
pub fn is_remove_mode(&self) -> bool {
self.remove.is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KicadVersion {
V5,
V6,
}