diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 713a9596..41e3dfa7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: matrix: toolchain: - "stable" - - "1.63.0" + - "1.66.1" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/Cargo.toml b/Cargo.toml index 87e162ee..db01b97a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,5 @@ chrono = "0.4" num-rational = "0.4" thiserror = "2.0" uuid = "1" +strum = "0.27.1" +strum_macros = "0.27.1" diff --git a/src/attributes.rs b/src/attributes.rs index ed1a73e2..3544784a 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -1,6 +1,7 @@ //! Attributes. use std::io::Write; +use strum_macros::{IntoStaticStr, VariantArray, VariantNames}; use uuid::Uuid; use crate::errors::GerberResult; @@ -264,50 +265,28 @@ impl PartialGerberCode for FileAttribute { } // TextMode -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "UPPERCASE")] pub enum TextMode { + #[strum(serialize = "B")] BarCode, + #[strum(serialize = "C")] Characters, } -impl TextMode { - pub fn values() -> &'static [Self] { - &[Self::BarCode, Self::Characters] - } -} - -impl PartialGerberCode for TextMode { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - Self::Characters => write!(writer, "C")?, - Self::BarCode => write!(writer, "B")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(TextMode); // TextMirroring -#[derive(Debug, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "UPPERCASE")] pub enum TextMirroring { + #[strum(serialize = "R")] Readable, + #[strum(serialize = "M")] Mirrored, } -impl TextMirroring { - pub fn values() -> &'static [Self] { - &[Self::Readable, Self::Mirrored] - } -} - -impl PartialGerberCode for TextMirroring { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - Self::Readable => write!(writer, "R")?, - Self::Mirrored => write!(writer, "M")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(TextMirroring); // ApertureAttribute @@ -543,51 +522,45 @@ impl PartialGerberCode for Part { // Position -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + Hash, + strum_macros::Display, + IntoStaticStr, + VariantNames, + VariantArray, +)] +#[strum(serialize_all = "PascalCase")] pub enum Position { Top, + #[strum(serialize = "Bot")] Bottom, } -impl PartialGerberCode for Position { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - Position::Top => write!(writer, "Top")?, - Position::Bottom => write!(writer, "Bot")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(Position); // ExtendedPosition -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum ExtendedPosition { Top, + #[strum(serialize = "Inr")] Inner, + #[strum(serialize = "Bot")] Bottom, } -impl ExtendedPosition { - pub fn values() -> &'static [Self] { - &[Self::Top, Self::Inner, Self::Bottom] - } -} - -impl PartialGerberCode for ExtendedPosition { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - ExtendedPosition::Top => write!(writer, "Top")?, - ExtendedPosition::Inner => write!(writer, "Inr")?, - ExtendedPosition::Bottom => write!(writer, "Bot")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(ExtendedPosition); // CopperType -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum CopperType { Plane, Signal, @@ -595,101 +568,58 @@ pub enum CopperType { Hatched, } -impl CopperType { - pub fn values() -> &'static [Self] { - &[Self::Plane, Self::Signal, Self::Mixed, Self::Hatched] - } -} - -impl PartialGerberCode for CopperType { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - CopperType::Plane => write!(writer, "Plane")?, - CopperType::Signal => write!(writer, "Signal")?, - CopperType::Mixed => write!(writer, "Mixed")?, - CopperType::Hatched => write!(writer, "Hatched")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(CopperType); // PlatedDrill -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum PlatedDrill { + #[strum(serialize = "PTH")] PlatedThroughHole, Blind, Buried, } -impl PartialGerberCode for PlatedDrill { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - PlatedDrill::PlatedThroughHole => write!(writer, "PTH")?, - PlatedDrill::Blind => write!(writer, "Blind")?, - PlatedDrill::Buried => write!(writer, "Buried")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(PlatedDrill); // NonPlatedDrill -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum NonPlatedDrill { + #[strum(serialize = "NPTH")] NonPlatedThroughHole, Blind, Buried, } -impl PartialGerberCode for NonPlatedDrill { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - NonPlatedDrill::NonPlatedThroughHole => write!(writer, "NPTH")?, - NonPlatedDrill::Blind => write!(writer, "Blind")?, - NonPlatedDrill::Buried => write!(writer, "Buried")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(NonPlatedDrill); // DrillRouteType -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum DrillRouteType { Drill, + #[strum(serialize = "Rout")] Route, Mixed, } -impl PartialGerberCode for DrillRouteType { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - DrillRouteType::Drill => write!(writer, "Drill")?, - DrillRouteType::Route => write!(writer, "Rout")?, - DrillRouteType::Mixed => write!(writer, "Mixed")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(DrillRouteType); // Profile -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] pub enum Profile { + #[strum(serialize = "P")] Plated, + #[strum(serialize = "NP")] NonPlated, } -impl PartialGerberCode for Profile { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - Profile::Plated => write!(writer, "P")?, - Profile::NonPlated => write!(writer, "NP")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(Profile); // FileFunction @@ -776,21 +706,14 @@ pub enum FileFunction { // FilePolarity -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum FilePolarity { Positive, Negative, } -impl PartialGerberCode for FilePolarity { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - FilePolarity::Positive => write!(writer, "Positive")?, - FilePolarity::Negative => write!(writer, "Negative")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(FilePolarity); // GenerationSoftware @@ -873,63 +796,31 @@ pub enum ApertureFunction { Drawing, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum IPC4761ViaProtection { Ia, Ib, IIa, IIb, + #[strum(serialize = "IIIa")] IIIa, + #[strum(serialize = "IIIb")] IIIb, IVa, IVb, V, + #[strum(serialize = "VI")] VI, + #[strum(serialize = "VII")] VII, None, } -impl IPC4761ViaProtection { - pub fn values() -> &'static [Self] { - &[ - Self::Ia, - Self::Ib, - Self::IIa, - Self::IIb, - Self::IIIa, - Self::IIIb, - Self::IVa, - Self::IVb, - Self::V, - Self::VI, - Self::VII, - Self::None, - ] - } -} +impl_partial_gerber_code_via_strum!(IPC4761ViaProtection); -impl PartialGerberCode for IPC4761ViaProtection { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - let code = match self { - IPC4761ViaProtection::Ia => "Ia", - IPC4761ViaProtection::Ib => "Ib", - IPC4761ViaProtection::IIa => "IIa", - IPC4761ViaProtection::IIb => "IIb", - IPC4761ViaProtection::IIIa => "IIIa", - IPC4761ViaProtection::IIIb => "IIIb", - IPC4761ViaProtection::IVa => "IVa", - IPC4761ViaProtection::IVb => "IVb", - IPC4761ViaProtection::V => "V", - IPC4761ViaProtection::VI => "VI", - IPC4761ViaProtection::VII => "VII", - IPC4761ViaProtection::None => "None", - }; - write!(writer, "{}", code)?; - Ok(()) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum ComponentOutline { Body, Lead2Lead, @@ -937,127 +828,56 @@ pub enum ComponentOutline { Courtyard, } -impl ComponentOutline { - pub fn values() -> &'static [Self] { - &[ - Self::Body, - Self::Lead2Lead, - Self::Footprint, - Self::Courtyard, - ] - } -} +impl_partial_gerber_code_via_strum!(ComponentOutline); -impl PartialGerberCode for ComponentOutline { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - let code = match self { - ComponentOutline::Body => "Body", - ComponentOutline::Lead2Lead => "Lead2Lead", - ComponentOutline::Footprint => "Footprint", - ComponentOutline::Courtyard => "Courtyard", - }; - write!(writer, "{}", code)?; - Ok(()) - } -} // DrillFunction -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum DrillFunction { + #[strum(serialize = "Breakout")] BreakOut, Tooling, Other, } -impl DrillFunction { - pub fn values() -> &'static [Self] { - &[Self::BreakOut, Self::Tooling, Self::Other] - } -} - -impl PartialGerberCode for DrillFunction { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - DrillFunction::Tooling => write!(writer, "Tooling")?, - DrillFunction::BreakOut => write!(writer, "BreakOut")?, - DrillFunction::Other => write!(writer, "Other")?, - } - - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(DrillFunction); // ComponentDrill -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// 2024.05 spec mismatch warning: Aperture function ".AperFunction.ComponentDill" has "PressFit" (uppercase F) whereas Component Characteristics ".CMnt" has "Pressfit" (lowercase f)" +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] pub enum ComponentDrill { + #[strum(serialize = "PressFit")] PressFit, } -impl ComponentDrill { - pub fn values() -> &'static [Self] { - &[Self::PressFit] - } -} - -impl PartialGerberCode for ComponentDrill { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - ComponentDrill::PressFit => write!(writer, "PressFit")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(ComponentDrill); // SmdPadType -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum SmdPadType { + #[strum(serialize = "CuDef")] CopperDefined, + #[strum(serialize = "SMDef")] SoldermaskDefined, } -impl SmdPadType { - pub fn values() -> &'static [Self] { - &[Self::CopperDefined, Self::SoldermaskDefined] - } -} - -impl PartialGerberCode for SmdPadType { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - SmdPadType::CopperDefined => write!(writer, "CuDef")?, - SmdPadType::SoldermaskDefined => write!(writer, "SMDef")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(SmdPadType); // FiducialScope -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "PascalCase")] pub enum FiducialScope { Local, Global, Panel, } -impl FiducialScope { - pub fn values() -> &'static [Self] { - &[Self::Local, Self::Global, Self::Panel] - } -} - -impl PartialGerberCode for FiducialScope { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - FiducialScope::Global => write!(writer, "Global")?, - FiducialScope::Local => write!(writer, "Local")?, - FiducialScope::Panel => write!(writer, "Panel")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(FiducialScope); // ObjectAttribute #[derive(Debug, Clone, PartialEq)] @@ -1099,8 +919,8 @@ impl PartialGerberCode for ObjectAttribute { } } -// ComponentCharacteristics -// 2024.05 - 5.6.1.6 "Cxxx (Component Characteristics)" +/// ComponentCharacteristics +/// 2024.05 - 5.6.1.6 "Cxxx (Component Characteristics)" #[derive(Debug, Clone, PartialEq)] pub enum ComponentCharacteristics { /// ".CRot," @@ -1178,32 +998,22 @@ impl PartialGerberCode for ComponentCharacteristics { Ok(()) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + +/// 2024.05 spec mismatch warning: Aperture function ".AperFunction.ComponentDill" has "PressFit" (uppercase F) whereas Component Characteristics ".CMnt" has "Pressfit" (lowercase f)" +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] pub enum ComponentMounting { + #[strum(serialize = "TH")] ThroughHole, /// Surface mount device + #[strum(serialize = "SMD")] SMD, + #[strum(serialize = "Pressfit")] PressFit, + #[strum(serialize = "Other")] Other, } -impl ComponentMounting { - pub fn values() -> &'static [Self] { - &[Self::ThroughHole, Self::SMD, Self::PressFit, Self::Other] - } -} - -impl PartialGerberCode for ComponentMounting { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match self { - ComponentMounting::ThroughHole => write!(writer, "TH")?, - ComponentMounting::SMD => write!(writer, "SMD")?, - ComponentMounting::PressFit => write!(writer, "Pressfit")?, - ComponentMounting::Other => write!(writer, "Other")?, - } - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(ComponentMounting); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SupplierPart { diff --git a/src/extended_codes.rs b/src/extended_codes.rs index 9926039d..800ca7eb 100644 --- a/src/extended_codes.rs +++ b/src/extended_codes.rs @@ -5,24 +5,21 @@ use std::io::Write; use crate::errors::GerberResult; use crate::traits::PartialGerberCode; use crate::MacroDecimal; +use strum_macros; +use strum_macros::{IntoStaticStr, VariantArray, VariantNames}; // Unit -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "UPPERCASE")] pub enum Unit { + #[strum(serialize = "IN")] Inches, + #[strum(serialize = "MM")] Millimeters, } -impl PartialGerberCode for Unit { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - Unit::Millimeters => write!(writer, "MM")?, - Unit::Inches => write!(writer, "IN")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(Unit); // ApertureDefinition @@ -221,43 +218,30 @@ impl PartialGerberCode for Polygon { // Polarity -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "UPPERCASE")] pub enum Polarity { + #[strum(serialize = "C")] Clear, + #[strum(serialize = "D")] Dark, } -impl PartialGerberCode for Polarity { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - Polarity::Clear => write!(writer, "C")?, - Polarity::Dark => write!(writer, "D")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(Polarity); // Mirroring -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)] +#[strum(serialize_all = "UPPERCASE")] pub enum Mirroring { + #[strum(serialize = "N")] None, X, Y, XY, } -impl PartialGerberCode for Mirroring { - fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { - match *self { - Mirroring::None => write!(writer, "N")?, - Mirroring::X => write!(writer, "X")?, - Mirroring::Y => write!(writer, "Y")?, - Mirroring::XY => write!(writer, "XY")?, - }; - Ok(()) - } -} +impl_partial_gerber_code_via_strum!(Mirroring); // Scaling diff --git a/src/lib.rs b/src/lib.rs index f77d8c90..4d38420d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,15 +14,17 @@ //! //! - [`GerberCode`](trait.GerberCode.html) generates a full Gerber code line, //! terminated with a newline character. -//! - [`PartialGerberCode`](trait.PartialGerberCode.html) generates Gerber representation of a +//! - `PartialGerberCode` (internal only) generates Gerber representation of a //! value, but does not represent a full line of code. -//! This should only be used by developers implementing parsers. If you need it for another reason, please let us know. #![allow(clippy::new_without_default)] #[cfg(test)] #[macro_use] mod test_macros; +#[macro_use] +mod serialization_macros; + mod attributes; mod codegen; mod coordinates; @@ -39,7 +41,7 @@ pub use crate::errors::*; pub use crate::extended_codes::*; pub use crate::function_codes::*; pub use crate::macros::*; -pub use crate::traits::{GerberCode, PartialGerberCode}; +pub use crate::traits::GerberCode; pub use crate::types::*; // re-export some types @@ -885,7 +887,7 @@ mod serialization_tests { function: Some(DrillFunction::BreakOut), }, )); - assert_code!(function, "%TA.AperFunction,MechanicalDrill,BreakOut*%\n"); + assert_code!(function, "%TA.AperFunction,MechanicalDrill,Breakout*%\n"); let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction( ApertureFunction::MechanicalDrill { diff --git a/src/serialization_macros.rs b/src/serialization_macros.rs new file mode 100644 index 00000000..cab32526 --- /dev/null +++ b/src/serialization_macros.rs @@ -0,0 +1,11 @@ +macro_rules! impl_partial_gerber_code_via_strum { + ($name:ident) => { + impl PartialGerberCode for $name { + fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> { + let value: &'static str = self.into(); + write!(writer, "{}", value)?; + Ok(()) + } + } + }; +}