cowirc 0.2.0

Asychronous IRCv3 library for Rust
Documentation
/*
 * This file is part of CowIRC.
 *
 * CowIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CowIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with CowIRC. If not, see <http://www.gnu.org/licenses/>.
 */

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attribute {
    Bold,
    Italic,
    Underline,
    Colour(u8),
}

#[must_use]
pub fn decoder(input: &str) -> Vec<(String, Vec<Attribute>)> {
    let mut output = Vec::new();
    let mut current_text = String::new();
    let mut current_attributes = Vec::new();
    let mut attribute_stack = Vec::new();

    let mut chars = input.chars().peekable();
    while let Some(c) = chars.next() {
        match c {
            '\x02' => {
                if current_attributes.contains(&Attribute::Bold) {
                    current_attributes.retain(|&a| a != Attribute::Bold);
                    attribute_stack.pop();
                } else {
                    current_attributes.push(Attribute::Bold);
                    attribute_stack.push(Attribute::Bold);
                }
            }
            '\x1D' => {
                if current_attributes.contains(&Attribute::Italic) {
                    current_attributes.retain(|&a| a != Attribute::Italic);
                    attribute_stack.pop();
                } else {
                    current_attributes.push(Attribute::Italic);
                    attribute_stack.push(Attribute::Italic);
                }
            }
            '\x1F' => {
                if current_attributes.contains(&Attribute::Underline) {
                    current_attributes.retain(|&a| a != Attribute::Underline);
                    attribute_stack.pop();
                } else {
                    current_attributes.push(Attribute::Underline);
                    attribute_stack.push(Attribute::Underline);
                }
            }
            '\x03' => {
                let mut color_code = String::new();
                if let Some(next_char) = chars.peek() {
                    if next_char.is_ascii_digit() {
                        color_code.push(*next_char);
                        chars.next();
                    }
                }
                if let Some(next_char) = chars.peek() {
                    if next_char.is_ascii_digit() {
                        color_code.push(*next_char);
                        chars.next();
                    }
                }
                if !color_code.is_empty() {
                    let color_num = color_code.parse::<u8>().unwrap_or(0);
                    current_attributes.push(Attribute::Colour(color_num));
                    attribute_stack.push(Attribute::Colour(color_num));
                }
            }
            '\x0F' => {
                current_attributes.clear();
                attribute_stack.clear();
            }
            _ => current_text.push(c),
        }

        if !attribute_stack.is_empty() {
            current_attributes = attribute_stack.clone();
        }

        if !current_text.is_empty() {
            output.push((current_text.clone(), current_attributes.clone()));
            current_text.clear();
        }
    }

    if !current_text.is_empty() {
        output.push((current_text, current_attributes));
    }

    output
}