Skip to main content

use_pin/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, num::NonZeroU32, str::FromStr};
5use std::error::Error;
6
7use use_component::ReferenceDesignator;
8
9/// Commonly used pin primitives.
10pub mod prelude {
11    pub use crate::{
12        PinIdentifier, PinName, PinNameError, PinNumber, PinNumberError, PinPolarity,
13        PinPolarityParseError, PinRef, PinRole, PinRoleParseError,
14    };
15}
16
17/// A one-based package or component pin number.
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct PinNumber(NonZeroU32);
20
21impl PinNumber {
22    /// Creates a non-zero pin number.
23    ///
24    /// # Errors
25    ///
26    /// Returns [`PinNumberError::Zero`] when `value` is zero.
27    pub fn new(value: u32) -> Result<Self, PinNumberError> {
28        NonZeroU32::new(value).map(Self).ok_or(PinNumberError::Zero)
29    }
30
31    /// Returns the pin number.
32    #[must_use]
33    pub const fn get(self) -> u32 {
34        self.0.get()
35    }
36}
37
38impl From<NonZeroU32> for PinNumber {
39    fn from(value: NonZeroU32) -> Self {
40        Self(value)
41    }
42}
43
44impl TryFrom<u32> for PinNumber {
45    type Error = PinNumberError;
46
47    fn try_from(value: u32) -> Result<Self, Self::Error> {
48        Self::new(value)
49    }
50}
51
52impl fmt::Display for PinNumber {
53    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54        self.get().fmt(formatter)
55    }
56}
57
58/// Errors returned while constructing pin numbers.
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum PinNumberError {
61    /// Pin number zero is not accepted.
62    Zero,
63}
64
65impl fmt::Display for PinNumberError {
66    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Zero => formatter.write_str("pin number must be non-zero"),
69        }
70    }
71}
72
73impl Error for PinNumberError {}
74
75/// A descriptive pin name such as `VCC`, `GND`, `SDA`, or `RESET`.
76#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub struct PinName(String);
78
79impl PinName {
80    /// Creates a pin name from non-empty text.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`PinNameError::Empty`] when the trimmed value is empty.
85    pub fn new(value: impl AsRef<str>) -> Result<Self, PinNameError> {
86        let trimmed = value.as_ref().trim();
87        if trimmed.is_empty() {
88            Err(PinNameError::Empty)
89        } else {
90            Ok(Self(trimmed.to_string()))
91        }
92    }
93
94    /// Returns the pin name text.
95    #[must_use]
96    pub fn as_str(&self) -> &str {
97        &self.0
98    }
99
100    /// Consumes the pin name and returns the owned string.
101    #[must_use]
102    pub fn into_string(self) -> String {
103        self.0
104    }
105}
106
107impl AsRef<str> for PinName {
108    fn as_ref(&self) -> &str {
109        self.as_str()
110    }
111}
112
113impl fmt::Display for PinName {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        formatter.write_str(self.as_str())
116    }
117}
118
119impl FromStr for PinName {
120    type Err = PinNameError;
121
122    fn from_str(value: &str) -> Result<Self, Self::Err> {
123        Self::new(value)
124    }
125}
126
127/// Errors returned while constructing pin names.
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum PinNameError {
130    /// The pin name was empty after trimming whitespace.
131    Empty,
132}
133
134impl fmt::Display for PinNameError {
135    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::Empty => formatter.write_str("pin name cannot be empty"),
138        }
139    }
140}
141
142impl Error for PinNameError {}
143
144/// Descriptive electronic pin roles.
145#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
146pub enum PinRole {
147    Input,
148    Output,
149    Bidirectional,
150    Power,
151    Ground,
152    Clock,
153    Reset,
154    Enable,
155    NoConnect,
156    Unknown,
157    Custom(String),
158}
159
160impl fmt::Display for PinRole {
161    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162        formatter.write_str(match self {
163            Self::Input => "input",
164            Self::Output => "output",
165            Self::Bidirectional => "bidirectional",
166            Self::Power => "power",
167            Self::Ground => "ground",
168            Self::Clock => "clock",
169            Self::Reset => "reset",
170            Self::Enable => "enable",
171            Self::NoConnect => "no-connect",
172            Self::Unknown => "unknown",
173            Self::Custom(value) => value.as_str(),
174        })
175    }
176}
177
178impl FromStr for PinRole {
179    type Err = PinRoleParseError;
180
181    fn from_str(value: &str) -> Result<Self, Self::Err> {
182        let trimmed = value.trim();
183        if trimmed.is_empty() {
184            return Err(PinRoleParseError::Empty);
185        }
186
187        match normalized_token(trimmed).as_str() {
188            "input" | "in" => Ok(Self::Input),
189            "output" | "out" => Ok(Self::Output),
190            "bidirectional" | "bidirectional-io" | "io" => Ok(Self::Bidirectional),
191            "power" => Ok(Self::Power),
192            "ground" | "gnd" => Ok(Self::Ground),
193            "clock" | "clk" => Ok(Self::Clock),
194            "reset" => Ok(Self::Reset),
195            "enable" => Ok(Self::Enable),
196            "no-connect" | "nc" => Ok(Self::NoConnect),
197            "unknown" => Ok(Self::Unknown),
198            _ => Ok(Self::Custom(trimmed.to_string())),
199        }
200    }
201}
202
203/// Errors returned while parsing pin roles.
204#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205pub enum PinRoleParseError {
206    /// The role was empty after trimming whitespace.
207    Empty,
208}
209
210impl fmt::Display for PinRoleParseError {
211    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
212        match self {
213            Self::Empty => formatter.write_str("pin role cannot be empty"),
214        }
215    }
216}
217
218impl Error for PinRoleParseError {}
219
220/// Descriptive pin polarity vocabulary.
221#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
222pub enum PinPolarity {
223    ActiveHigh,
224    ActiveLow,
225    NonInverting,
226    Inverting,
227    Unknown,
228}
229
230impl fmt::Display for PinPolarity {
231    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
232        formatter.write_str(match self {
233            Self::ActiveHigh => "active-high",
234            Self::ActiveLow => "active-low",
235            Self::NonInverting => "non-inverting",
236            Self::Inverting => "inverting",
237            Self::Unknown => "unknown",
238        })
239    }
240}
241
242impl FromStr for PinPolarity {
243    type Err = PinPolarityParseError;
244
245    fn from_str(value: &str) -> Result<Self, Self::Err> {
246        let trimmed = value.trim();
247        if trimmed.is_empty() {
248            return Err(PinPolarityParseError::Empty);
249        }
250
251        match normalized_token(trimmed).as_str() {
252            "active-high" => Ok(Self::ActiveHigh),
253            "active-low" => Ok(Self::ActiveLow),
254            "non-inverting" => Ok(Self::NonInverting),
255            "inverting" => Ok(Self::Inverting),
256            "unknown" => Ok(Self::Unknown),
257            _ => Err(PinPolarityParseError::Unknown),
258        }
259    }
260}
261
262/// Errors returned while parsing pin polarity.
263#[derive(Clone, Copy, Debug, Eq, PartialEq)]
264pub enum PinPolarityParseError {
265    /// The polarity text was empty after trimming whitespace.
266    Empty,
267    /// The polarity was not part of the fixed vocabulary.
268    Unknown,
269}
270
271impl fmt::Display for PinPolarityParseError {
272    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
273        match self {
274            Self::Empty => formatter.write_str("pin polarity cannot be empty"),
275            Self::Unknown => formatter.write_str("unknown pin polarity"),
276        }
277    }
278}
279
280impl Error for PinPolarityParseError {}
281
282/// A pin identified by number or name.
283#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
284pub enum PinIdentifier {
285    Number(PinNumber),
286    Name(PinName),
287}
288
289impl From<PinNumber> for PinIdentifier {
290    fn from(value: PinNumber) -> Self {
291        Self::Number(value)
292    }
293}
294
295impl From<PinName> for PinIdentifier {
296    fn from(value: PinName) -> Self {
297        Self::Name(value)
298    }
299}
300
301impl fmt::Display for PinIdentifier {
302    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
303        match self {
304            Self::Number(number) => number.fmt(formatter),
305            Self::Name(name) => name.fmt(formatter),
306        }
307    }
308}
309
310/// A reference to a component pin, such as `U2:VCC` or `R1:1`.
311#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
312pub struct PinRef {
313    component: ReferenceDesignator,
314    pin: PinIdentifier,
315}
316
317impl PinRef {
318    /// Creates a pin reference from a component designator and pin identifier.
319    #[must_use]
320    pub const fn new(component: ReferenceDesignator, pin: PinIdentifier) -> Self {
321        Self { component, pin }
322    }
323
324    /// Creates a pin reference from a numeric pin.
325    #[must_use]
326    pub const fn numbered(component: ReferenceDesignator, pin: PinNumber) -> Self {
327        Self::new(component, PinIdentifier::Number(pin))
328    }
329
330    /// Creates a pin reference from a named pin.
331    #[must_use]
332    pub const fn named(component: ReferenceDesignator, pin: PinName) -> Self {
333        Self::new(component, PinIdentifier::Name(pin))
334    }
335
336    /// Returns the component reference designator.
337    #[must_use]
338    pub const fn component(&self) -> &ReferenceDesignator {
339        &self.component
340    }
341
342    /// Returns the pin identifier.
343    #[must_use]
344    pub const fn pin(&self) -> &PinIdentifier {
345        &self.pin
346    }
347}
348
349impl fmt::Display for PinRef {
350    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
351        write!(formatter, "{}:{}", self.component, self.pin)
352    }
353}
354
355fn normalized_token(value: &str) -> String {
356    value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
357}
358
359#[cfg(test)]
360mod tests {
361    use super::{
362        PinName, PinNameError, PinNumber, PinNumberError, PinPolarity, PinRole, PinRoleParseError,
363    };
364
365    #[test]
366    fn accepts_valid_pin_numbers() -> Result<(), PinNumberError> {
367        let number = PinNumber::new(1)?;
368
369        assert_eq!(number.get(), 1);
370        assert_eq!(number.to_string(), "1");
371        Ok(())
372    }
373
374    #[test]
375    fn rejects_zero_pin_numbers() {
376        assert_eq!(PinNumber::new(0), Err(PinNumberError::Zero));
377    }
378
379    #[test]
380    fn accepts_valid_pin_names() -> Result<(), PinNameError> {
381        let name = PinName::new("RESET")?;
382
383        assert_eq!(name.as_str(), "RESET");
384        assert_eq!(name.to_string(), "RESET");
385        Ok(())
386    }
387
388    #[test]
389    fn rejects_empty_pin_names() {
390        assert_eq!(PinName::new(" "), Err(PinNameError::Empty));
391    }
392
393    #[test]
394    fn displays_and_parses_pin_roles() -> Result<(), PinRoleParseError> {
395        assert_eq!("input".parse::<PinRole>()?, PinRole::Input);
396        assert_eq!("NC".parse::<PinRole>()?, PinRole::NoConnect);
397        assert_eq!(PinRole::Power.to_string(), "power");
398        Ok(())
399    }
400
401    #[test]
402    fn displays_and_parses_pin_polarity() -> Result<(), Box<dyn std::error::Error>> {
403        assert_eq!("active low".parse::<PinPolarity>()?, PinPolarity::ActiveLow);
404        assert_eq!(PinPolarity::NonInverting.to_string(), "non-inverting");
405        Ok(())
406    }
407}