use crate::core::{is_unique, RUMResult};
use chardetng::EncodingDetector;
pub use compact_str::{
format_compact as rumtk_format, CompactString, CompactStringExt, ToCompactString,
};
use encoding_rs::Encoding;
use unicode_segmentation::UnicodeSegmentation;
const ESCAPED_STRING_WINDOW: usize = 6;
const ASCII_ESCAPE_CHAR: char = '\\';
const MIN_ASCII_READABLE: char = ' ';
const MAX_ASCII_READABLE: char = '~';
pub const EMPTY_STRING: &str = "";
pub const DOT_STR: &str = ".";
pub const EMPTY_STRING_OPTION: Option<&str> = Some("");
pub const READABLE_ASCII: &str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
pub type RUMString = CompactString;
pub trait UTFStringExtensions {
fn count_graphemes(&self) -> usize;
fn get_grapheme(&self, index: usize) -> &str;
fn get_graphemes(&self) -> Vec<&str>;
fn get_grapheme_chunk(&self, offset: usize) -> Vec<&str>;
#[inline(always)]
fn take_grapheme<'a>(&self, graphemes: &Vec<&'a str>, index: usize) -> RUMString {
if index >= graphemes.len() {
return RUMString::from(EMPTY_STRING);
}
RUMString::from(graphemes[index])
}
#[inline(always)]
fn get_grapheme_window(&self, min: usize, max: usize, offset: usize) -> RUMString {
let mut window: RUMString = RUMString::with_capacity(max - min);
let start = min + offset;
let end = max + offset;
let graphemes = self.get_graphemes();
for i in start..end {
window += &self.take_grapheme(&graphemes, i);
}
window
}
#[inline(always)]
fn get_grapheme_string(&self, end_pattern: &str, offset: usize) -> RUMString {
let mut window: RUMString = RUMString::with_capacity(ESCAPED_STRING_WINDOW);
for grapheme in self.get_grapheme_chunk(offset) {
if grapheme == end_pattern {
return RUMString::from(window);
} else {
window += grapheme;
}
}
RUMString::from(window)
}
#[inline(always)]
fn find_grapheme(&self, pattern: &str, offset: usize) -> &str {
for grapheme in self.get_grapheme_chunk(offset) {
if grapheme == pattern {
return grapheme;
}
}
EMPTY_STRING
}
#[inline(always)]
fn truncate(&self, max_size: usize) -> RUMString {
self.get_grapheme_window(0, max_size, 0)
}
}
pub trait AsStr {
fn as_str(&self) -> &str;
}
pub trait RUMStringConversions: ToString {
fn to_rumstring(&self) -> RUMString {
RUMString::from(self.to_string())
}
fn to_raw(&self) -> Vec<u8> {
self.to_string().as_bytes().to_vec()
}
}
pub trait StringUtils: AsStr + UTFStringExtensions {
#[inline(always)]
fn duplicate(&self, count: usize) -> RUMString {
let mut duplicated = RUMString::with_capacity(count);
for i in 0..count {
duplicated += &self.as_str();
}
duplicated
}
fn is_unique(&self) -> bool {
let graphemes = self.get_graphemes();
is_unique(&graphemes)
}
}
impl UTFStringExtensions for RUMString {
#[inline(always)]
fn count_graphemes(&self) -> usize {
self.graphemes(true).count()
}
#[inline(always)]
fn get_grapheme(&self, index: usize) -> &str {
self.graphemes(true)
.nth(index)
.or(EMPTY_STRING_OPTION)
.unwrap()
}
#[inline(always)]
fn get_graphemes(&self) -> Vec<&str> {
self.graphemes(true).collect::<Vec<&str>>()
}
#[inline(always)]
fn get_grapheme_chunk(&self, offset: usize) -> Vec<&str> {
self.graphemes(true).skip(offset).collect::<Vec<&str>>()
}
}
impl RUMStringConversions for RUMString {}
impl AsStr for RUMString {
fn as_str(&self) -> &str {
self.as_str()
}
}
impl StringUtils for RUMString {}
impl UTFStringExtensions for str {
#[inline(always)]
fn count_graphemes(&self) -> usize {
self.graphemes(true).count()
}
#[inline(always)]
fn get_grapheme(&self, index: usize) -> &str {
self.graphemes(true)
.nth(index)
.or(EMPTY_STRING_OPTION)
.unwrap()
}
#[inline(always)]
fn get_graphemes(&self) -> Vec<&str> {
self.graphemes(true).collect::<Vec<&str>>()
}
#[inline(always)]
fn get_grapheme_chunk(&self, offset: usize) -> Vec<&str> {
self.graphemes(true).skip(offset).collect::<Vec<&str>>()
}
}
impl RUMStringConversions for str {}
impl AsStr for str {
fn as_str(&self) -> &str {
self
}
}
impl StringUtils for str {}
impl RUMStringConversions for char {}
pub trait RUMArrayConversions {
fn to_rumstring(&self) -> RUMString;
}
impl RUMArrayConversions for Vec<u8> {
fn to_rumstring(&self) -> RUMString {
self.as_slice().to_rumstring()
}
}
impl RUMArrayConversions for &[u8] {
fn to_rumstring(&self) -> RUMString {
RUMString::from_utf8(&self).unwrap()
}
}
pub fn count_tokens_ignoring_pattern(vector: &Vec<&str>, string_token: &RUMString) -> usize {
let mut count: usize = 0;
for tok in vector.iter() {
if string_token != tok {
count += 1;
}
}
count
}
pub fn try_decode(src: &[u8]) -> RUMString {
let mut detector = EncodingDetector::new();
detector.feed(&src, true);
let encoding = detector.guess(None, true);
decode(src, encoding)
}
pub fn try_decode_with(src: &[u8], encoding_name: &str) -> RUMString {
let encoding = match Encoding::for_label(encoding_name.as_bytes()) {
Some(v) => v,
None => return RUMString::from(""),
};
decode(src, encoding)
}
fn decode(src: &[u8], encoding: &'static Encoding) -> RUMString {
match encoding.decode_without_bom_handling_and_without_replacement(&src) {
Some(res) => RUMString::from(res),
None => RUMString::from_utf8(src).unwrap(),
}
}
pub fn unescape_string(escaped_str: &str) -> RUMResult<RUMString> {
let graphemes = escaped_str.graphemes(true).collect::<Vec<&str>>();
let str_size = graphemes.len();
let mut result: Vec<u8> = Vec::with_capacity(escaped_str.len());
let mut i = 0;
while i < str_size {
let seq_start = graphemes[i];
match seq_start {
"\\" => {
let escape_seq = get_grapheme_string(&graphemes, " ", i);
let mut c = match unescape(&escape_seq) {
Ok(c) => c,
Err(_why) => Vec::from(escape_seq.as_bytes()),
};
result.append(&mut c);
i += &escape_seq.count_graphemes();
}
_ => {
result.append(&mut Vec::from(seq_start.as_bytes()));
i += 1;
}
}
}
Ok(try_decode(result.as_slice()))
}
pub fn get_grapheme_string<'a>(
graphemes: &Vec<&'a str>,
end_grapheme: &str,
start_index: usize,
) -> RUMString {
get_grapheme_collection(graphemes, end_grapheme, start_index).join_compact("")
}
pub fn get_grapheme_collection<'a>(
graphemes: &Vec<&'a str>,
end_grapheme: &str,
start_index: usize,
) -> Vec<&'a str> {
let mut result: Vec<&'a str> = Vec::new();
for grapheme in graphemes.iter().skip(start_index) {
let item = *grapheme;
if item == end_grapheme {
break;
}
result.push(item);
}
result
}
pub fn unescape(escaped_str: &str) -> Result<Vec<u8>, RUMString> {
let lower_case = escaped_str.to_lowercase();
let mut bytes: Vec<u8> = Vec::with_capacity(3);
match &lower_case[0..2] {
"\\x" => {
let byte_str = number_to_char_unchecked(&hex_to_number(&lower_case[2..6])?);
bytes.append(&mut byte_str.as_bytes().to_vec());
}
"\\u" => {
let byte_str = number_to_char_unchecked(&hex_to_number(&lower_case[2..6])?);
bytes.append(&mut byte_str.as_bytes().to_vec());
}
"\\c" => {
let byte_str = number_to_char_unchecked(&hex_to_number(&lower_case[2..6])?);
bytes.append(&mut byte_str.as_bytes().to_vec());
}
"\\o" => {
let byte_str = number_to_char_unchecked(&octal_to_number(&lower_case[2..6])?);
bytes.append(&mut byte_str.as_bytes().to_vec());
}
"\\m" => match lower_case.count_graphemes() {
8 => {
bytes.push(hex_to_byte(&lower_case[2..4])?);
bytes.push(hex_to_byte(&lower_case[4..6])?);
bytes.push(hex_to_byte(&lower_case[6..8])?);
}
6 => {
bytes.push(hex_to_byte(&lower_case[2..4])?);
bytes.push(hex_to_byte(&lower_case[4..6])?);
}
_ => {
return Err(rumtk_format!(
"Unknown multibyte sequence. Cannot decode {}",
lower_case
))
}
},
"\\z" => bytes.append(&mut lower_case.as_bytes().to_vec()),
_ => bytes.push(unescape_control_byte(&lower_case[0..2])?),
}
Ok(bytes)
}
fn unescape_control(escaped_str: &str) -> Result<char, RUMString> {
match escaped_str {
"\\t" => Ok('\t'),
"\\b" => Ok('\x08'),
"\\n" => Ok('\n'),
"\\r" => Ok('\r'),
"\\f" => Ok('\x14'),
"\\s" => Ok('\x20'),
"\\\\" => Ok(ASCII_ESCAPE_CHAR),
"\\'" => Ok('\''),
"\\\"" => Ok('"'),
"\\0" => Ok('\0'),
"\\v" => Ok('\x0B'),
"\\a" => Ok('\x07'),
_ => Err(rumtk_format!(
"Unknown escape sequence? Sequence: {}!",
escaped_str
)),
}
}
fn unescape_control_byte(escaped_str: &str) -> Result<u8, RUMString> {
match escaped_str {
"\\t" => Ok(9), "\\b" => Ok(8), "\\n" => Ok(10), "\\r" => Ok(13), "\\f" => Ok(12), "\\s" => Ok(32), "\\\\" => Ok(27), "\\'" => Ok(39), "\\\"" => Ok(34), "\\0" => Ok(0), "\\v" => Ok(11), "\\a" => Ok(7), _ => hex_to_byte(escaped_str),
}
}
fn hex_to_number(hex_str: &str) -> Result<u32, RUMString> {
match u32::from_str_radix(&hex_str, 16) {
Ok(result) => Ok(result),
Err(val) => Err(rumtk_format!(
"Failed to parse string with error {}! Input string {} \
is not hex string!",
val,
hex_str
)),
}
}
fn hex_to_byte(hex_str: &str) -> Result<u8, RUMString> {
match u8::from_str_radix(&hex_str, 16) {
Ok(result) => Ok(result),
Err(val) => Err(rumtk_format!(
"Failed to parse string with error {}! Input string {} \
is not hex string!",
val,
hex_str
)),
}
}
fn octal_to_number(hoctal_str: &str) -> Result<u32, RUMString> {
match u32::from_str_radix(&hoctal_str, 8) {
Ok(result) => Ok(result),
Err(val) => Err(rumtk_format!(
"Failed to parse string with error {}! Input string {} \
is not an octal string!",
val,
hoctal_str
)),
}
}
fn octal_to_byte(hoctal_str: &str) -> Result<u8, RUMString> {
match u8::from_str_radix(&hoctal_str, 8) {
Ok(result) => Ok(result),
Err(val) => Err(rumtk_format!(
"Failed to parse string with error {}! Input string {} \
is not an octal string!",
val,
hoctal_str
)),
}
}
fn number_to_char(num: &u32) -> Result<RUMString, RUMString> {
match char::from_u32(*num) {
Some(result) => Ok(result.to_rumstring()),
None => Err(rumtk_format!(
"Failed to cast number to character! Number {}",
num
)),
}
}
fn number_to_char_unchecked(num: &u32) -> RUMString {
unsafe { char::from_u32_unchecked(*num).to_rumstring() }
}
pub fn escape(unescaped_str: &str) -> RUMString {
basic_escape(unescaped_str)
.replace("{", "")
.replace("}", "")
.to_rumstring()
}
pub fn basic_escape(unescaped_str: &str) -> RUMString {
let escaped = is_escaped_str(unescaped_str);
if !escaped {
return unescaped_str.escape_default().to_compact_string();
}
unescaped_str.to_rumstring()
}
pub fn is_ascii_str(unescaped_str: &str) -> bool {
unescaped_str.is_ascii()
}
pub fn is_escaped_str(unescaped_str: &str) -> bool {
if !is_ascii_str(unescaped_str) {
return false;
}
for c in unescaped_str.chars() {
if !is_printable_char(&c) {
return false;
}
}
true
}
pub fn is_printable_char(c: &char) -> bool {
&MIN_ASCII_READABLE <= c && c <= &MAX_ASCII_READABLE
}
pub fn filter_ascii(unescaped_str: &str, closure: fn(char) -> bool) -> RUMString {
let mut filtered = unescaped_str.to_rumstring();
filtered.retain(closure);
filtered
}
pub fn filter_non_printable_ascii(unescaped_str: &str) -> RUMString {
filter_ascii(unescaped_str, |c: char| is_printable_char(&c))
}