| #![allow(dead_code)] |
|
|
| use std::ops::Range; |
| |
| const MAX_LIMIT: usize = 40_000; |
|
|
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub struct ClipperResult<'a> { |
| |
| pub actual: &'a str, |
| |
| pub prefix: Option<Range<usize>>, |
| |
| pub suffix: Option<Range<usize>>, |
| } |
|
|
| impl ClipperResult<'_> { |
| |
| pub fn is_truncated(&self) -> bool { |
| self.prefix.is_some() || self.suffix.is_some() |
| } |
|
|
| |
| pub fn prefix_content(&self) -> Option<&str> { |
| self.prefix |
| .as_ref() |
| .and_then(|range| self.actual.get(range.clone())) |
| } |
|
|
| |
| pub fn suffix_content(&self) -> Option<&str> { |
| self.suffix |
| .as_ref() |
| .and_then(|range| self.actual.get(range.clone())) |
| } |
| } |
|
|
| |
| |
| |
| |
| #[derive(Clone, Debug, PartialEq, Eq)] |
| pub enum Clipper { |
| |
| Prefix(usize), |
|
|
| |
| |
| |
| PrefixSuffix(usize, usize), |
|
|
| |
| Suffix(usize), |
| } |
|
|
| impl Default for Clipper { |
| |
| |
| fn default() -> Self { |
| Self::Prefix(MAX_LIMIT) |
| } |
| } |
|
|
| impl Clipper { |
| |
| |
| pub fn from_start(prefix_chars: usize) -> Clipper { |
| Self::Prefix(prefix_chars) |
| } |
|
|
| |
| |
| pub fn from_end(suffix_chars: usize) -> Clipper { |
| Self::Suffix(suffix_chars) |
| } |
|
|
| |
| |
| pub fn from_start_end(start: usize, end: usize) -> Clipper { |
| Self::PrefixSuffix(start, end) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| pub fn clip(self, content: &str) -> ClipperResult<'_> { |
| |
| if content.is_empty() { |
| return ClipperResult { prefix: None, suffix: None, actual: content }; |
| } |
|
|
| |
| let char_count = content.chars().count(); |
|
|
| |
| match self { |
| Clipper::Prefix(limit) => self.apply_prefix(content, char_count, limit), |
| Clipper::Suffix(limit) => self.apply_suffix(content, char_count, limit), |
| Clipper::PrefixSuffix(prefix_limit, suffix_limit) => { |
| self.apply_prefix_suffix(content, char_count, prefix_limit, suffix_limit) |
| } |
| } |
| } |
|
|
| |
| fn apply_prefix<'a>( |
| &self, |
| content: &'a str, |
| char_count: usize, |
| limit: usize, |
| ) -> ClipperResult<'a> { |
| if char_count <= limit { |
| return ClipperResult { prefix: None, suffix: None, actual: content }; |
| } |
|
|
| |
| let byte_idx = content |
| .char_indices() |
| .nth(limit) |
| .map_or(content.len(), |(idx, _)| idx); |
|
|
| ClipperResult { prefix: Some(0..byte_idx), suffix: None, actual: content } |
| } |
|
|
| |
| fn apply_suffix<'a>( |
| &self, |
| content: &'a str, |
| char_count: usize, |
| limit: usize, |
| ) -> ClipperResult<'a> { |
| if char_count <= limit { |
| return ClipperResult { prefix: None, suffix: None, actual: content }; |
| } |
|
|
| |
| let start_idx = content |
| .char_indices() |
| .nth(char_count - limit) |
| .map_or(0, |(idx, _)| idx); |
|
|
| ClipperResult { |
| prefix: None, |
| suffix: Some(start_idx..content.len()), |
| actual: content, |
| } |
| } |
|
|
| |
| fn apply_prefix_suffix<'a>( |
| &self, |
| content: &'a str, |
| char_count: usize, |
| prefix_limit: usize, |
| suffix_limit: usize, |
| ) -> ClipperResult<'a> { |
| |
| |
| if prefix_limit + suffix_limit >= char_count { |
| return ClipperResult { prefix: None, suffix: None, actual: content }; |
| } |
|
|
| |
| let prefix_end_idx = content |
| .char_indices() |
| .nth(prefix_limit) |
| .map_or(content.len(), |(idx, _)| idx); |
|
|
| |
| let suffix_start_idx = content |
| .char_indices() |
| .nth(char_count - suffix_limit) |
| .map_or(0, |(idx, _)| idx); |
|
|
| ClipperResult { |
| prefix: Some(0..prefix_end_idx), |
| suffix: Some(suffix_start_idx..content.len()), |
| actual: content, |
| } |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_truncate_strategy_start() { |
| let content = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(10); |
| let strategy = Clipper::Prefix(10); |
|
|
| let result = strategy.clip(&content); |
|
|
| |
| assert!(result.prefix.is_some()); |
| let range = result.prefix.unwrap(); |
| assert_eq!(&content[range], "ABCDEFGHIJ"); |
| assert!(result.suffix.is_none()); |
| } |
|
|
| #[test] |
| fn test_truncate_strategy_end() { |
| let content = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(10); |
| let strategy = Clipper::Suffix(10); |
|
|
| let result = strategy.clip(&content); |
|
|
| |
| assert!(result.suffix.is_some()); |
| let range = result.suffix.unwrap(); |
| assert_eq!(&content[range], "QRSTUVWXYZ"); |
| assert!(result.prefix.is_none()); |
| } |
|
|
| #[test] |
| fn test_truncate_strategy_both() { |
| let content = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(10); |
| let strategy = Clipper::PrefixSuffix(10, 10); |
|
|
| let result = strategy.clip(&content); |
|
|
| |
| assert!(result.prefix.is_some()); |
| assert!(result.suffix.is_some()); |
| let prefix_range = result.prefix.unwrap(); |
| let suffix_range = result.suffix.unwrap(); |
| assert_eq!(&content[prefix_range], "ABCDEFGHIJ"); |
| assert_eq!(&content[suffix_range], "QRSTUVWXYZ"); |
| } |
|
|
| #[test] |
| fn test_truncate_within_limit() { |
| let content = "Short content"; |
| let strategy = Clipper::Prefix(100); |
|
|
| let result = strategy.clip(content); |
|
|
| |
| assert!(result.prefix.is_none()); |
| assert!(result.suffix.is_none()); |
| assert_eq!(result.actual, content); |
| } |
|
|
| #[test] |
| fn test_truncate_strategy_both_overlapping() { |
| let content = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| let strategy = Clipper::PrefixSuffix(15, 15); |
|
|
| let result = strategy.clip(content); |
|
|
| |
| |
| assert!(result.prefix.is_none()); |
| assert!(result.suffix.is_none()); |
| assert_eq!(result.actual, content); |
| } |
| } |
|
|