use clap::Subcommand;
use serde::Serialize;
use std::path::PathBuf;
use crate::output::{self, TextFormat};
use altium_format::ops::pcblib;
#[derive(Subcommand)]
pub enum PcbLibCommands {
Overview {
path: PathBuf,
},
List {
path: PathBuf,
},
Search {
path: PathBuf,
query: String,
#[arg(short, long)]
limit: Option<usize>,
},
Info {
path: PathBuf,
},
Footprint {
path: PathBuf,
name: String,
#[arg(long)]
primitives: bool,
},
Pads {
path: PathBuf,
#[arg(short, long)]
footprint: Option<String>,
},
Primitives {
path: PathBuf,
footprint: String,
},
Holes {
path: PathBuf,
},
Measure {
path: PathBuf,
footprint: String,
},
Json {
path: PathBuf,
#[arg(long)]
full: bool,
},
RenderAscii {
path: PathBuf,
footprint: String,
},
Create {
path: PathBuf,
},
AddFootprint {
path: PathBuf,
name: String,
#[arg(short, long)]
description: Option<String>,
},
AddPad {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short, long)]
designator: String,
#[arg(short, long)]
x: f64,
#[arg(short, long)]
y: f64,
#[arg(short, long)]
width: f64,
#[arg(long)]
height: f64,
#[arg(short, long, default_value = "rectangular")]
shape: String,
#[arg(long, default_value = "0")]
hole: f64,
},
AddSilkscreen {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(long)]
x1: f64,
#[arg(long)]
y1: f64,
#[arg(long)]
x2: f64,
#[arg(long)]
y2: f64,
#[arg(short, long, default_value = "0.15")]
width: f64,
},
AddArc {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short, long)]
x: f64,
#[arg(short, long)]
y: f64,
#[arg(short, long)]
radius: f64,
#[arg(long)]
start_angle: f64,
#[arg(long)]
end_angle: f64,
#[arg(short, long, default_value = "0.15")]
width: f64,
},
GenChip {
path: PathBuf,
size: String,
#[arg(short, long, default_value = "nominal")]
density: String,
},
RenderSvg {
path: PathBuf,
footprint: String,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long, default_value = "0.5")]
scale: f64,
#[arg(long)]
light: bool,
#[arg(long)]
no_grid: bool,
#[arg(long)]
no_designators: bool,
},
RenderPng {
path: PathBuf,
footprint: String,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long, default_value = "0.5")]
scale: f64,
#[arg(short, long)]
width: Option<u32>,
},
AddJson {
path: PathBuf,
#[arg(short, long)]
file: Option<String>,
#[arg(short, long)]
json: Option<String>,
},
AddPadRow {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short, long)]
count: usize,
#[arg(short, long)]
pitch: String,
#[arg(long)]
pad_width: String,
#[arg(long)]
pad_height: String,
#[arg(short, long, default_value = "horizontal")]
direction: String,
#[arg(short, long, default_value = "1")]
start: u32,
#[arg(short, long, default_value = "0mm")]
x: String,
#[arg(short, long, default_value = "0mm")]
y: String,
#[arg(long, default_value = "rectangular")]
shape: String,
#[arg(long, default_value = "0mm")]
hole: String,
#[arg(long)]
use_spacing: bool,
},
AddDualRow {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short = 'n', long)]
pads_per_side: usize,
#[arg(short, long)]
pitch: String,
#[arg(short, long)]
row_spacing: String,
#[arg(long)]
pad_width: Option<String>,
#[arg(long)]
pad_height: Option<String>,
#[arg(long)]
pad_diameter: Option<String>,
#[arg(long)]
hole: Option<String>,
#[arg(long, default_value = "rectangular")]
shape: String,
},
AddQuadPads {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short = 'n', long)]
pads_per_side: usize,
#[arg(short, long)]
pitch: String,
#[arg(short, long)]
span: String,
#[arg(long)]
pad_width: String,
#[arg(long)]
pad_height: String,
#[arg(long, default_value = "rectangular")]
shape: String,
},
AddPadGrid {
path: PathBuf,
#[arg(short, long)]
footprint: String,
#[arg(short, long)]
rows: usize,
#[arg(short, long)]
cols: usize,
#[arg(short, long)]
pitch: String,
#[arg(long)]
pad_diameter: String,
#[arg(long, default_value = "round")]
shape: String,
#[arg(long, default_value = "0mm")]
skip_center: String,
},
}
pub fn run(cmd: &PcbLibCommands, format: &str) -> Result<(), Box<dyn std::error::Error>> {
match cmd {
PcbLibCommands::Overview { path } => {
let result = pcblib::cmd_overview(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::List { path } => {
let result = pcblib::cmd_list(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Search {
path,
query,
limit: _,
} => {
let result = pcblib::cmd_search(path, query)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Info { path } => {
let result = pcblib::cmd_info(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Footprint {
path,
name,
primitives,
} => {
let result = pcblib::cmd_footprint(path, name, *primitives)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Pads { path, footprint } => {
let result = pcblib::cmd_pads(path, footprint.clone(), false)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Primitives { path, footprint } => {
let result = pcblib::cmd_primitives(path, footprint)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Holes { path } => {
let result = pcblib::cmd_holes(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbLibCommands::Measure { path, footprint } => {
pcblib::cmd_measure(path, footprint, "summary", None, None, None, format == "json")?;
}
PcbLibCommands::Json { path, full } => {
let result = pcblib::cmd_json(path, *full)?;
let json_str = serde_json::to_string_pretty(&result)?;
println!("{}", json_str);
}
PcbLibCommands::RenderAscii { path, footprint } => {
pcblib::cmd_render_ascii(path, footprint, 80, 40)?;
}
PcbLibCommands::Create { path } => {
pcblib::cmd_create(path)?;
}
PcbLibCommands::AddFootprint {
path,
name,
description,
} => {
pcblib::cmd_add_footprint(path, name, description.clone())?;
}
PcbLibCommands::AddPad {
path,
footprint,
designator,
x,
y,
width,
height,
shape,
hole,
} => {
pcblib::cmd_add_pad(
path,
footprint,
designator,
*x,
*y,
*width,
*height,
shape,
*hole,
)?;
}
PcbLibCommands::AddSilkscreen {
path,
footprint,
x1,
y1,
x2,
y2,
width,
} => {
pcblib::cmd_add_silkscreen(path, footprint, *x1, *y1, *x2, *y2, *width)?;
}
PcbLibCommands::AddArc {
path,
footprint,
x,
y,
radius,
start_angle,
end_angle,
width,
} => {
pcblib::cmd_add_arc(
path,
footprint,
*x,
*y,
*radius,
*start_angle,
*end_angle,
*width,
)?;
}
PcbLibCommands::GenChip {
path,
size,
density,
} => {
pcblib::cmd_gen_chip(path, size, density)?;
}
PcbLibCommands::RenderSvg {
path,
footprint,
output,
scale,
light,
no_grid,
no_designators,
} => {
pcblib::cmd_render_svg(
path,
footprint,
output.clone(),
*scale,
*light,
*no_grid,
*no_designators,
)?;
}
PcbLibCommands::RenderPng {
path,
footprint,
output,
scale,
width,
} => {
pcblib::cmd_render_png(path, footprint, output.clone(), *scale, *width)?;
}
PcbLibCommands::AddJson { path, file, json } => {
pcblib::cmd_add_json(path, file.clone(), json.clone())?;
}
PcbLibCommands::AddPadRow {
path,
footprint,
count,
pitch,
pad_width,
pad_height,
direction,
start,
x,
y,
shape,
hole,
use_spacing,
} => {
pcblib::cmd_add_pad_row(
path,
footprint,
*count,
pitch,
pad_width,
pad_height,
direction,
*start,
x,
y,
shape,
hole,
*use_spacing,
)?;
}
PcbLibCommands::AddDualRow {
path,
footprint,
pads_per_side,
pitch,
row_spacing,
pad_width,
pad_height,
pad_diameter,
hole,
shape,
} => {
pcblib::cmd_add_dual_row(
path,
footprint,
*pads_per_side,
pitch,
row_spacing,
pad_width.as_deref(),
pad_height.as_deref(),
pad_diameter.as_deref(),
hole.as_deref(),
shape,
)?;
}
PcbLibCommands::AddQuadPads {
path,
footprint,
pads_per_side,
pitch,
span,
pad_width,
pad_height,
shape,
} => {
pcblib::cmd_add_quad_pads(
path,
footprint,
*pads_per_side,
pitch,
span,
pad_width,
pad_height,
shape,
)?;
}
PcbLibCommands::AddPadGrid {
path,
footprint,
rows,
cols,
pitch,
pad_diameter,
shape,
skip_center,
} => {
pcblib::cmd_add_pad_grid(
path,
footprint,
*rows,
*cols,
pitch,
pad_diameter,
shape,
skip_center,
)?;
}
}
Ok(())
}
#[derive(Serialize)]
#[serde(transparent)]
struct TextWrapper<T>(T);
impl<T: Serialize> TextFormat for TextWrapper<T> {
fn format_text(&self) -> String {
if let Ok(value) = serde_json::to_value(&self.0) {
format_value(&value, 0)
} else {
"Error formatting output".to_string()
}
}
}
fn format_value(value: &serde_json::Value, indent: usize) -> String {
let prefix = " ".repeat(indent);
match value {
serde_json::Value::Object(map) => {
let mut out = String::new();
for (key, val) in map {
match val {
serde_json::Value::String(s) => {
out.push_str(&format!("{}{}: {}\n", prefix, key, s));
}
serde_json::Value::Number(n) => {
out.push_str(&format!("{}{}: {}\n", prefix, key, n));
}
serde_json::Value::Bool(b) => {
out.push_str(&format!("{}{}: {}\n", prefix, key, b));
}
serde_json::Value::Null => {
out.push_str(&format!("{}{}: null\n", prefix, key));
}
serde_json::Value::Array(arr) => {
if arr.is_empty() {
out.push_str(&format!("{}{}: []\n", prefix, key));
} else {
out.push_str(&format!("{}{}:\n", prefix, key));
for item in arr {
out.push_str(&format_value(item, indent + 1));
out.push('\n');
}
}
}
serde_json::Value::Object(_) => {
out.push_str(&format!("{}{}:\n", prefix, key));
out.push_str(&format_value(val, indent + 1));
}
}
}
out
}
serde_json::Value::Array(arr) => {
let mut out = String::new();
for (i, item) in arr.iter().enumerate() {
out.push_str(&format!("{}[{}]\n", prefix, i));
out.push_str(&format_value(item, indent + 1));
}
out
}
serde_json::Value::String(s) => format!("{}{}\n", prefix, s),
serde_json::Value::Number(n) => format!("{}{}\n", prefix, n),
serde_json::Value::Bool(b) => format!("{}{}\n", prefix, b),
serde_json::Value::Null => format!("{}null\n", prefix),
}
}