p101_enc 0.11.0

Library to convert Olivetti P101 program to and from different encodings
Documentation
#[derive(Debug, Default)]
pub struct CodePage437 {
    skip: bool,
    trim: bool
}

impl CodePage437 {
    pub fn new() -> Self {
        Self {
            skip: false,
            trim: true
        }
    }

    pub fn new_with_option(remove_empty_lines: bool) -> Self {
        Self {
            skip: false,
            trim: remove_empty_lines
        }
    }

    pub fn map(&mut self, raw: &[u8]) -> String {
        let buffer_size = raw.len() * 2;
        let mut buffer = String::with_capacity(buffer_size);
        let mut pos = 0;

        while pos < raw.len() {
            let (step, c) = self.map_bytes(raw, pos);

            pos += step;

            if let Some(c) = c {
                if !self.skip {
                    buffer += &c.to_string();
                }
            }
        }

        if self.trim {
            self.remove_empty_lines(buffer)
        } else if buffer.ends_with('\n') {
            buffer
        } else {
            buffer + "\n"
        }
    }

    fn map_bytes(&mut self, raw: &[u8], pos: usize) -> (usize, Option<char>) {
        match raw[pos] {
            65 => (1, Some('A')),
            66 => (1, Some('B')),
            67 => (1, Some('C')),
            68 => (1, Some('D')),
            69 => (1, Some('E')),
            70 => (1, Some('F')),
            77 => (1, Some('M')),
            82 => (1, Some('R')),
            83 => (1, Some('S')),
            86 => (1, Some('V')),
            87 => (1, Some('W')),
            88 => (1, Some('X')),
            89 => (1, Some('Y')),
            90 => (1, Some('Z')),

            48 => (1, Some('0')),
            49 => (1, Some('1')),
            50 => (1, Some('2')),
            51 => (1, Some('3')),
            52 => (1, Some('4')),
            53 => (1, Some('5')),
            54 => (1, Some('6')),
            55 => (1, Some('7')),
            56 => (1, Some('8')),
            57 => (1, Some('9')),

            4 => (1, Some('')),
            23 => (1, Some('')),
            24 => (1, Some('')),
            25 => (1, Some('')),
            35 => (1, Some('#')),
            42 => (1, Some('*')),
            43 => (1, Some('+')),
            45 => (1, Some('-')),
            47 => (1, Some('/')),
            120 => (1, Some('x')),
            246 => (1, Some('÷')),
            251 => (1, Some('')),
 
            10 => {
                self.skip = false;
                (1, Some('\n'))
            },
            13 => {
                self.skip = false;
                if pos + 1 < raw.len() && raw[pos + 1] == 10 {
                    (2, Some('\n'))
                } else {
                    (1, Some('\n'))
                }
            },
            39 => {
                self.skip = true;
                (1, None)
            },

            _ => (1, None)
        }
    }

    fn remove_empty_lines(&self, input: String) -> String {
        input.lines()
            .filter(|s| !s.is_empty())
            .map(|s| format!("{}\n", s))
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn map_upper_alpha() {
        let input: Vec<u8> = vec![65, 66, 67, 68, 69, 70, 77, 82, 83, 86, 87, 88, 89, 90];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("ABCDEFMRSVWXYZ\n", output);
    }

    #[test]
    pub fn map_numbers() {
        let input: Vec<u8> = vec![48, 49, 50, 51, 52, 53, 54, 55, 56, 57];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("0123456789\n", output);
    }

    #[test]
    pub fn map_symbols() {
        let input: Vec<u8> = vec![4, 23, 24, 25, 42, 43, 45, 47, 120, 246, 251];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("⋄↕↑↓*+-/x÷√\n", output);
    }

    #[test]
    pub fn skip_everything_after_an_apostrophe() {
        let input: Vec<u8> = vec![65, 66, 67, 32, 39, 68, 69, 70, 77, 82, 83, 86, 87, 88, 89, 90];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("ABC\n", output);
    }

    #[test]
    pub fn skip_everything_after_an_apostrophe_until_newline() {
        let input: Vec<u8> = vec![65, 66, 67, 39, 68, 69, 10, 65];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("ABC\nA\n", output);
    }

    #[test]
    pub fn break_lines_after_carriage_return_or_line_feed_or_both() {
        let input: Vec<u8> = vec![65, 10, 66, 13, 67, 10, 13, 68];
        let mut cp = CodePage437::new();

        let output = cp.map(&input);

        assert_eq!("A\nB\nC\nD\n", output);
    }

    #[test]
    pub fn preserve_empty_lines() {
        let input: Vec<u8> = vec![65, 10, 66, 13, 67, 13, 10, 10, 68];
        let mut cp = CodePage437::new_with_option(false);

        let output = cp.map(&input);

        assert_eq!("A\nB\nC\n\nD\n", output);
    }
}