aa_colour/
lib.rs

1/*! AA-colour
2
3Add colours to your amino acids in the terminal.
4
5Should work with Linux, MacOS and Windows.
6
7```rust
8use aa_colour::{AaColour, palettes::Clustal};
9
10println!("{}", AaColour::colour::<Clustal>('A').unwrap());
11println!("{}", AaColour::blank::<Clustal>('-').unwrap());
12```
13*/
14// #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
15
16pub mod error;
17pub mod palettes;
18
19use core::fmt;
20use error::AaColourError;
21use palettes::{Palette, Rgb};
22
23/// Amino acid background colour
24#[derive(Debug, Clone)]
25pub struct AaColour {
26    aa: char,
27    colour: &'static Rgb,
28}
29
30impl fmt::Display for AaColour {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.write_fmt(format_args!(
33            "\x1B[48;2;{};{};{};30m",
34            self.colour.r, self.colour.g, self.colour.b
35        ))?;
36        fmt::Display::fmt(&self.aa, f)?;
37        f.write_str("\x1b[0m")
38    }
39}
40
41impl AaColour {
42    /// Coloured amino acid according to its name
43    /// # Errors
44    ///
45    /// Fails if `x` is not a 1-letter code for an amino acid
46    /// # Example
47    ///
48    /// ```rust
49    /// use aa_colour::{AaColour, palettes::Clustal};
50    ///
51    /// let coloured_aa = AaColour::colour::<Clustal>('C');
52    /// ````
53    pub fn colour<P: Palette>(x: char) -> Result<Self, AaColourError> {
54        let col = match_char_aa::<P>(x)?;
55
56        Ok(Self { aa: x, colour: col })
57    }
58
59    /// Uncoloured amino acid
60    /// # Errors
61    ///
62    /// Fails if `x` is not a 1-letter code for an amino acid (TODO)
63    /// # Example
64    ///
65    /// ```rust
66    /// use aa_colour::{AaColour, palettes::Clustal};
67    ///
68    /// let blank_aa = AaColour::blank::<Clustal>('C');
69    /// ````
70    pub fn blank<P: Palette>(x: char) -> Result<Self, AaColourError> {
71        Ok(Self {
72            aa: x,
73            colour: P::GAP,
74        })
75    }
76}
77
78macro_rules! match_aa {
79    ($(
80        $aa:literal $colour:ident
81    ),* $(,)?) => {
82        fn match_char_aa<P: Palette>(x: char) -> Result<&'static Rgb, AaColourError> {
83            match x.to_ascii_uppercase() {
84                $(
85                    $aa => Ok(P::$colour),
86                )*
87                _ => Err(AaColourError::NotAnAminoAcid(x)),
88            }
89        }
90    };
91}
92
93match_aa! {
94    'A' A,
95    'C' C,
96    'D' D,
97    'E' E,
98    'F' F,
99    'G' G,
100    'H' H,
101    'I' I,
102    'K' K,
103    'L' L,
104    'M' M,
105    'N' N,
106    'P' P,
107    'Q' Q,
108    'R' R,
109    'S' S,
110    'T' T,
111    'V' V,
112    'W' W,
113    'Y' Y,
114    '-' GAP
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120    use crate::palettes::Clustal;
121
122    #[test]
123    fn all_amino_acids_clustal() {
124        let aas = vec![
125            'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
126            'V', 'W', '-',
127        ];
128        for aa in aas {
129            print!("{}", AaColour::colour::<Clustal>(aa).unwrap());
130        }
131        println!(); //
132    }
133
134    #[test]
135    fn all_amino_acids_clustal_blanks() {
136        let aas = vec![
137            'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
138            'V', 'W', '-',
139        ];
140        for aa in aas {
141            print!("{}", AaColour::blank::<Clustal>(aa).unwrap());
142        }
143        println!(); //
144    }
145}