// This file is part of CW2
// Copyright (C) 2021 Soni L.
//
// This program 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.
//
// This program 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 this program. If not, see .
use std::convert::TryFrom;
/// Converts the given reason and content to a CW2.
///
/// A CW2 is a string of `[CW (reason)](content)`.
pub fn to_cw2(reason: &str, content: &str) -> String {
let expected_size = reason.len() + content.len() + "[CW ]".len();
let mut sb = String::with_capacity(expected_size);
let mut count: isize = 0;
let iter = reason.matches(|_| true);
iter.for_each(|c| {
match c {
"\x10" => {
sb += "\x10\x10";
},
"[" => {
count += 1;
sb += c;
},
"]" => {
count -= 1;
if count < 0 {
sb += "\x10";
count = 0;
}
sb += c;
},
c => {
sb += c;
},
}
});
if count > 0 {
let size = sb.len() + usize::try_from(count).unwrap();
let mut sb2 = String::with_capacity(size);
for c in sb.matches(|_| true).rev() {
match c {
"[" if count > 0 => {
count -= 1;
sb2 += c;
sb2 += "\x10";
},
c => {
sb2 += c;
},
}
}
sb = String::with_capacity(sb2.len());
sb.extend(sb2.matches(|_| true).rev());
}
format!("[CW {}]{}", sb, content)
}
/// Attempts to convert the given message to a CW2.
///
/// The message is expected to be in the format `[(reason)](content)`. Returns
/// `None` if it isn't, otherwise returns `[CW (reason)](content)`.
pub fn try_to_cw2(message: &str) -> Option {
parse_cw_helper(message, false).map(|(r, c)| to_cw2(&r, c))
}
/// Parses an incoming message for CW.
///
/// Splits `[CW (reason)](content)` into `(reason)` and `(content)`, or
/// returns `None`.
pub fn try_parse_cw2(message: &str) -> Option<(String, &str)> {
parse_cw_helper(message, true)
}
/// Parses a message for CW, with a flag to check for `CW` in the reason.
fn parse_cw_helper(message: &str, check_cw: bool) -> Option<(String, &str)> {
let mut count = 0;
let mut mquote = false;
let mut last_size = 0;
let mut skipped = if check_cw {
message.starts_with("[CW ").then(|| 4)?
} else {
1
};
let cw_start = skipped;
// figure out the last pos we need
let pos = message.match_indices(|_| true).take_while(|&(_, c)| {
match c {
"[" if !mquote => count += 1,
"]" if !mquote => count -= 1,
"\x10" if !mquote => mquote = true,
_ if mquote => {
mquote = false;
skipped += 1;
},
_ => {},
}
last_size = c.len();
count != 0
}).last()?.0;
(count == 0).then(|| {
// at this point we have `[CW foo]bar` as `[CW foo` and `bar`, but
// `pos` is only the start of the last `o` in `foo`.
let start_of_contents = pos + last_size + 1;
// build the string
let mut sb = String::with_capacity(pos - skipped);
let mut mquote = false;
let iter = message[cw_start..(start_of_contents-1)].matches(|_| true);
iter.for_each(|c| {
match c {
"\x10" if !mquote => mquote = true,
c => {
mquote = false;
sb += c;
},
}
});
let reason = sb;
let contents = &message[start_of_contents..];
(reason, contents)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_correct_cw_parsing() {
let tests = [
("[CW FOO] BAR", true, Some(("FOO".into(), " BAR"))),
("[CW FOO\x10]] BAR", true, Some(("FOO]".into(), " BAR"))),
("[CW FOO\x10[] BAR", true, Some(("FOO[".into(), " BAR"))),
("[CW FOO\x10] BAR", true, None),
("message", true, None),
("[CW FOO] BAR", false, Some(("CW FOO".into(), " BAR"))),
("[CW FOO\x10]] BAR", false, Some(("CW FOO]".into(), " BAR"))),
("[CW FOO\x10[] BAR", false, Some(("CW FOO[".into(), " BAR"))),
("[CW FOO\x10] BAR", false, None),
("message", false, None),
];
for test in &tests {
dbg!(test);
assert_eq!(parse_cw_helper(test.0, test.1), test.2);
}
}
#[test]
fn test_correct_cw_building() {
let tests = [
("", "", String::from("[CW ]")),
("[", "", "[CW \x10[]".into()),
("]", "", "[CW \x10]]".into()),
("[[]", "", "[CW [\x10[]]".into()),
("[]]", "", "[CW []\x10]]".into()),
];
for test in &tests {
dbg!(test);
assert_eq!(to_cw2(test.0, test.1), test.2);
}
}
}