use clap::Subcommand;
use serde::Serialize;
use std::path::PathBuf;
use crate::output::{self, TextFormat};
use altium_format::ops::pcbdoc;
#[derive(Subcommand)]
pub enum PcbDocCommands {
Overview {
path: PathBuf,
},
Info {
path: PathBuf,
},
Rules {
path: PathBuf,
#[arg(short, long)]
kind: Option<String>,
#[arg(short, long)]
verbose: bool,
},
Rule {
path: PathBuf,
name: String,
},
Components {
path: PathBuf,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
layer: Option<String>,
},
Component {
path: PathBuf,
designator: String,
},
Nets {
path: PathBuf,
},
Json {
path: PathBuf,
#[arg(long)]
full: bool,
#[arg(long)]
pretty: bool,
},
Create {
path: PathBuf,
#[arg(long)]
template: Option<PathBuf>,
},
Outline {
path: PathBuf,
},
SetOutlineRect {
path: PathBuf,
width: String,
height: String,
#[arg(long, default_value = "0mm")]
origin_x: String,
#[arg(long, default_value = "0mm")]
origin_y: String,
},
SetOutline {
path: PathBuf,
vertices: String,
},
Settings {
path: PathBuf,
},
SetSettings {
path: PathBuf,
#[arg(long)]
metric: bool,
#[arg(long)]
imperial: bool,
#[arg(long)]
snap_grid: Option<String>,
#[arg(long)]
visible_grid: Option<String>,
#[arg(long)]
component_grid: Option<String>,
#[arg(long)]
track_grid: Option<String>,
#[arg(long)]
via_grid: Option<String>,
#[arg(long)]
track_width: Option<String>,
#[arg(long)]
origin_x: Option<String>,
#[arg(long)]
origin_y: Option<String>,
},
Layers {
path: PathBuf,
#[arg(long)]
all: bool,
},
Keepouts {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddKeepout {
path: PathBuf,
layer: String,
x1: String,
y1: String,
x2: String,
y2: String,
},
Cutouts {
path: PathBuf,
},
AddCutout {
path: PathBuf,
x1: String,
y1: String,
x2: String,
y2: String,
},
Polygons {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
#[arg(short, long)]
net: Option<String>,
},
Polygon {
path: PathBuf,
index: usize,
},
AddPolygon {
path: PathBuf,
layer: String,
net: String,
vertices: String,
#[arg(long)]
pour_over: bool,
#[arg(long)]
remove_dead: bool,
#[arg(long, default_value = "solid")]
hatch_style: String,
},
Tracks {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddTrack {
path: PathBuf,
#[arg(long)]
start: Option<String>,
#[arg(long)]
end: Option<String>,
#[arg(long)]
start_pad: Option<String>,
#[arg(long)]
end_pad: Option<String>,
#[arg(short, long)]
width: Option<String>,
#[arg(short, long)]
layer: String,
#[arg(short, long)]
net: Option<String>,
},
AddTrackPath {
path: PathBuf,
vertices: String,
#[arg(short, long)]
width: Option<String>,
#[arg(short, long)]
layer: String,
#[arg(short, long)]
net: Option<String>,
},
Vias {
path: PathBuf,
},
AddVia {
path: PathBuf,
#[arg(long)]
at: Option<String>,
#[arg(long)]
at_pad: Option<String>,
#[arg(short, long)]
diameter: Option<String>,
#[arg(long)]
hole: Option<String>,
#[arg(long)]
from_layer: String,
#[arg(long)]
to_layer: String,
#[arg(short, long)]
net: Option<String>,
},
Arcs {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddArc {
path: PathBuf,
center: String,
radius: String,
start_angle: f64,
end_angle: f64,
#[arg(short, long)]
width: Option<String>,
#[arg(short, long)]
layer: String,
#[arg(short, long)]
net: Option<String>,
},
Fills {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddFill {
path: PathBuf,
x1y1: String,
x2y2: String,
#[arg(short, long)]
layer: String,
#[arg(short, long, default_value = "0")]
rotation: f64,
#[arg(short, long)]
net: Option<String>,
},
Texts {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddText {
path: PathBuf,
text: String,
at: String,
#[arg(long)]
height: Option<String>,
#[arg(short, long)]
layer: String,
#[arg(short, long, default_value = "0")]
rotation: f64,
},
Regions {
path: PathBuf,
#[arg(short, long)]
layer: Option<String>,
},
AddRegion {
path: PathBuf,
vertices: String,
#[arg(short, long)]
layer: String,
#[arg(long)]
keepout: bool,
#[arg(short, long)]
net: Option<String>,
},
PlaceComponent {
path: PathBuf,
designator: String,
#[arg(long)]
at: Option<String>,
#[arg(long)]
near: Option<String>,
#[arg(long)]
align_x: Option<String>,
#[arg(long)]
align_y: Option<String>,
#[arg(long)]
edge: Option<String>,
#[arg(long)]
offset: Option<String>,
#[arg(short, long)]
rotation: Option<f64>,
#[arg(short, long)]
layer: Option<String>,
#[arg(long)]
grid: Option<String>,
#[arg(long)]
force: bool,
},
AddComponent {
path: PathBuf,
schematic: PathBuf,
designator: String,
#[arg(long)]
footprint_lib: Option<PathBuf>,
#[arg(long)]
footprint: Option<String>,
#[arg(long)]
at: Option<String>,
#[arg(short, long, default_value = "top")]
layer: String,
},
AddNet {
path: PathBuf,
name: String,
},
AddRule {
path: PathBuf,
kind: String,
name: String,
#[arg(short, long, default_value = "1")]
priority: i32,
#[arg(long, default_value = "All")]
scope1: String,
#[arg(long, default_value = "All")]
scope2: String,
#[arg(long)]
gap: Option<String>,
#[arg(long)]
min_width: Option<String>,
#[arg(long)]
max_width: Option<String>,
#[arg(long)]
pref_width: Option<String>,
#[arg(long)]
comment: Option<String>,
#[arg(long)]
disabled: bool,
},
ModifyRule {
path: PathBuf,
name: String,
#[arg(short, long)]
priority: Option<i32>,
#[arg(long)]
gap: Option<String>,
#[arg(long)]
min_width: Option<String>,
#[arg(long)]
max_width: Option<String>,
#[arg(long)]
pref_width: Option<String>,
#[arg(long)]
comment: Option<String>,
#[arg(long)]
enable: bool,
#[arg(long)]
disable: bool,
},
DeleteRule {
path: PathBuf,
name: String,
},
}
pub fn run(cmd: &PcbDocCommands, format: &str) -> Result<(), Box<dyn std::error::Error>> {
match cmd {
PcbDocCommands::Overview { path } => {
let result = pcbdoc::cmd_overview(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Info { path } => {
let result = pcbdoc::cmd_info(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Rules {
path,
kind,
verbose,
} => {
let result = pcbdoc::cmd_rules(path, kind.clone(), *verbose)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Rule { path, name } => {
let result = pcbdoc::cmd_rule(path, name, true)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Components {
path,
verbose,
layer,
} => {
let result = pcbdoc::cmd_components(path, *verbose, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Component { path, designator } => {
let result = pcbdoc::cmd_component(path, designator, true)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Nets { path } => {
let result = pcbdoc::cmd_nets(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Json { path, full, pretty } => {
let result = pcbdoc::cmd_json(path, *full, *pretty)?;
let json_str = if *pretty {
serde_json::to_string_pretty(&result)?
} else {
serde_json::to_string(&result)?
};
println!("{}", json_str);
}
PcbDocCommands::Create { path, template } => {
pcbdoc::cmd_create(path, template.clone())?;
}
PcbDocCommands::Outline { path } => {
let result = pcbdoc::cmd_outline(path, false)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::SetOutlineRect {
path,
width,
height,
origin_x,
origin_y,
} => {
pcbdoc::cmd_set_outline_rect(path, width, height, origin_x, origin_y)?;
}
PcbDocCommands::SetOutline { path, vertices } => {
pcbdoc::cmd_set_outline(path, vertices)?;
}
PcbDocCommands::Settings { path } => {
let result = pcbdoc::cmd_settings(path, false)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::SetSettings {
path,
metric,
imperial,
snap_grid,
visible_grid,
component_grid,
track_grid,
via_grid,
track_width,
origin_x,
origin_y,
} => {
pcbdoc::cmd_set_settings(
path,
*metric,
*imperial,
snap_grid.clone(),
visible_grid.clone(),
component_grid.clone(),
track_grid.clone(),
via_grid.clone(),
track_width.clone(),
origin_x.clone(),
origin_y.clone(),
)?;
}
PcbDocCommands::Layers { path, all } => {
let result = pcbdoc::cmd_layers(path, *all)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Keepouts { path, layer } => {
let result = pcbdoc::cmd_keepouts(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddKeepout {
path,
layer,
x1,
y1,
x2,
y2,
} => {
pcbdoc::cmd_add_keepout(path, layer, x1, y1, x2, y2)?;
}
PcbDocCommands::Cutouts { path } => {
let result = pcbdoc::cmd_cutouts(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddCutout {
path,
x1,
y1,
x2,
y2,
} => {
pcbdoc::cmd_add_cutout(path, x1, y1, x2, y2)?;
}
PcbDocCommands::Polygons { path, layer, net } => {
let result = pcbdoc::cmd_polygons(path, layer.clone(), net.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::Polygon { path, index } => {
let result = pcbdoc::cmd_polygon(path, *index)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddPolygon {
path,
layer,
net,
vertices,
pour_over,
remove_dead,
hatch_style,
} => {
pcbdoc::cmd_add_polygon(
path,
layer,
net,
vertices,
*pour_over,
*remove_dead,
hatch_style,
)?;
}
PcbDocCommands::Tracks { path, layer } => {
let result = pcbdoc::cmd_tracks(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddTrack {
path,
start,
end,
start_pad,
end_pad,
width,
layer,
net,
} => {
pcbdoc::cmd_add_track(
path,
start.clone(),
end.clone(),
start_pad.clone(),
end_pad.clone(),
width.clone(),
layer,
net.clone(),
)?;
}
PcbDocCommands::AddTrackPath {
path,
vertices,
width,
layer,
net,
} => {
pcbdoc::cmd_add_track_path(path, vertices, width.clone(), layer, net.clone())?;
}
PcbDocCommands::Vias { path } => {
let result = pcbdoc::cmd_vias(path)?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddVia {
path,
at,
at_pad,
diameter,
hole,
from_layer,
to_layer,
net,
} => {
pcbdoc::cmd_add_via(
path,
at.clone(),
at_pad.clone(),
diameter.clone(),
hole.clone(),
from_layer,
to_layer,
net.clone(),
)?;
}
PcbDocCommands::Arcs { path, layer } => {
let result = pcbdoc::cmd_arcs(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddArc {
path,
center,
radius,
start_angle,
end_angle,
width,
layer,
net,
} => {
pcbdoc::cmd_add_arc(
path,
center,
radius,
*start_angle,
*end_angle,
width.clone(),
layer,
net.clone(),
)?;
}
PcbDocCommands::Fills { path, layer } => {
let result = pcbdoc::cmd_fills(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddFill {
path,
x1y1,
x2y2,
layer,
rotation,
net,
} => {
pcbdoc::cmd_add_fill(path, x1y1, x2y2, layer, *rotation, net.clone())?;
}
PcbDocCommands::Texts { path, layer } => {
let result = pcbdoc::cmd_texts(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddText {
path,
text,
at,
height,
layer,
rotation,
} => {
pcbdoc::cmd_add_text(path, text, at, height.clone(), layer, *rotation)?;
}
PcbDocCommands::Regions { path, layer } => {
let result = pcbdoc::cmd_regions(path, layer.clone())?;
output::print(&TextWrapper(result), format)?;
}
PcbDocCommands::AddRegion {
path,
vertices,
layer,
keepout,
net,
} => {
pcbdoc::cmd_add_region(path, vertices, layer, *keepout, net.clone())?;
}
PcbDocCommands::PlaceComponent {
path,
designator,
at,
near,
align_x,
align_y,
edge,
offset,
rotation,
layer,
grid,
force,
} => {
pcbdoc::cmd_place_component(
path,
designator,
at.clone(),
near.clone(),
align_x.clone(),
align_y.clone(),
edge.clone(),
offset.clone(),
*rotation,
layer.clone(),
grid.clone(),
*force,
)?;
}
PcbDocCommands::AddComponent {
path,
schematic,
designator,
footprint_lib,
footprint,
at,
layer,
} => {
pcbdoc::cmd_add_component(
path,
schematic,
designator,
footprint_lib.clone(),
footprint.clone(),
at.clone(),
layer,
)?;
}
PcbDocCommands::AddNet { path, name } => {
pcbdoc::cmd_add_net(path, name)?;
}
PcbDocCommands::AddRule {
path,
kind,
name,
priority,
scope1,
scope2,
gap,
min_width,
max_width,
pref_width,
comment,
disabled,
} => {
pcbdoc::cmd_add_rule(
path,
kind,
name,
*priority,
scope1,
scope2,
gap.clone(),
min_width.clone(),
max_width.clone(),
pref_width.clone(),
comment.clone(),
*disabled,
)?;
}
PcbDocCommands::ModifyRule {
path,
name,
priority,
gap,
min_width,
max_width,
pref_width,
comment,
enable,
disable,
} => {
pcbdoc::cmd_modify_rule(
path,
name,
*priority,
gap.clone(),
min_width.clone(),
max_width.clone(),
pref_width.clone(),
comment.clone(),
*enable,
*disable,
)?;
}
PcbDocCommands::DeleteRule { path, name } => {
pcbdoc::cmd_delete_rule(path, name)?;
}
}
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),
}
}