use crate::{
count_characters_on_last_line, FileSystem, SourceMap, SourceMapBuilder, SpanWithSource,
};
pub trait ToString {
fn push(&mut self, chr: char);
fn push_new_line(&mut self);
fn push_str(&mut self, string: &str);
fn push_str_contains_new_line(&mut self, string: &str);
fn add_mapping(&mut self, source_span: &SpanWithSource);
fn should_halt(&self) -> bool {
false
}
fn characters_on_current_line(&self) -> u32;
fn is_counting(&self) -> bool {
false
}
}
impl ToString for String {
fn push(&mut self, chr: char) {
self.push(chr);
}
fn push_new_line(&mut self) {
self.push('\n');
}
fn push_str(&mut self, string: &str) {
self.push_str(string)
}
fn push_str_contains_new_line(&mut self, string: &str) {
self.push_str(string)
}
fn add_mapping(&mut self, _source_span: &SpanWithSource) {}
fn characters_on_current_line(&self) -> u32 {
count_characters_on_last_line(self)
}
}
pub struct Writable<T: std::io::Write> {
pub writable: T,
pub length: u32,
pub since_new_line: u32,
pub source_map: Option<SourceMapBuilder>,
}
impl<T: std::io::Write> ToString for Writable<T> {
fn push(&mut self, chr: char) {
let mut buf = [0u8; 4]; let buf = chr.encode_utf8(&mut buf).as_bytes();
let char_size = chr.len_utf8();
self.length += char_size as u32;
self.since_new_line += char_size as u32;
self.writable.write_all(buf).unwrap();
}
fn push_new_line(&mut self) {
self.length += 1;
self.writable.write_all(&[b'\n']).unwrap();
}
fn push_str(&mut self, string: &str) {
self.length += string.len() as u32;
self.since_new_line += string.len() as u32;
self.writable.write_all(string.as_bytes()).unwrap();
}
fn push_str_contains_new_line(&mut self, slice: &str) {
self.length += slice.len() as u32;
self.writable.write_all(slice.as_bytes()).unwrap();
if let Some(ref mut sm) = self.source_map {
slice
.chars()
.filter(|chr| *chr == '\n')
.for_each(|_| sm.add_new_line());
}
self.since_new_line = count_characters_on_last_line(slice);
}
fn add_mapping(&mut self, source_span: &SpanWithSource) {
if let Some(ref mut sm) = self.source_map {
sm.add_mapping(source_span, self.since_new_line);
}
}
fn characters_on_current_line(&self) -> u32 {
self.since_new_line
}
}
#[derive(Default)]
pub struct StringWithOptionalSourceMap {
pub source: String,
pub source_map: Option<SourceMapBuilder>,
pub quit_after: Option<usize>,
pub since_new_line: u32,
}
impl StringWithOptionalSourceMap {
pub fn new(with_source_map: bool) -> Self {
Self {
source: String::new(),
source_map: with_source_map.then(SourceMapBuilder::new),
quit_after: None,
since_new_line: 0,
}
}
pub fn build(self, filesystem: &impl FileSystem) -> (String, Option<SourceMap>) {
(self.source, self.source_map.map(|sm| sm.build(filesystem)))
}
#[cfg(feature = "inline-source-map")]
pub fn build_with_inline_source_map(self, filesystem: &impl FileSystem) -> String {
use base64::Engine;
let Self {
mut source,
source_map,
quit_after: _,
since_new_line: _,
} = self;
let built_source_map = source_map.unwrap().build(filesystem);
source.push_str("\n//# sourceMappingURL=data:application/json;base64,");
source.push_str(
&base64::prelude::BASE64_STANDARD.encode(built_source_map.to_json(filesystem)),
);
source
}
}
impl ToString for StringWithOptionalSourceMap {
fn push(&mut self, chr: char) {
self.source.push(chr);
if let Some(ref mut sm) = self.source_map {
sm.add_to_column(chr.len_utf16());
}
self.since_new_line += chr.len_utf8() as u32;
}
fn push_new_line(&mut self) {
self.source.push('\n');
if let Some(ref mut sm) = self.source_map {
sm.add_new_line();
}
self.since_new_line = 0;
}
fn push_str(&mut self, slice: &str) {
self.source.push_str(slice);
if let Some(ref mut sm) = self.source_map {
sm.add_to_column(slice.chars().count());
}
self.since_new_line += slice.len() as u32;
}
fn push_str_contains_new_line(&mut self, slice: &str) {
self.source.push_str(slice);
if let Some(ref mut sm) = self.source_map {
slice
.chars()
.filter(|chr| *chr == '\n')
.for_each(|_| sm.add_new_line());
}
self.since_new_line = count_characters_on_last_line(slice);
}
fn add_mapping(&mut self, source_span: &SpanWithSource) {
if let Some(ref mut sm) = self.source_map {
sm.add_mapping(source_span, self.since_new_line);
}
}
fn should_halt(&self) -> bool {
self.quit_after
.map_or(false, |quit_after| self.source.len() > quit_after)
}
fn characters_on_current_line(&self) -> u32 {
self.since_new_line
}
fn is_counting(&self) -> bool {
self.quit_after.is_some()
}
}
pub struct Counter {
acc: usize,
max: usize,
}
impl Counter {
pub fn new(max: usize) -> Self {
Self { acc: 0, max }
}
pub fn get_count(&self) -> usize {
self.acc
}
}
impl ToString for Counter {
fn push(&mut self, chr: char) {
self.acc += chr.len_utf8();
}
fn push_new_line(&mut self) {
self.push('\n');
}
fn push_str(&mut self, string: &str) {
self.acc += string.len();
}
fn push_str_contains_new_line(&mut self, string: &str) {
self.acc += string.len();
}
fn add_mapping(&mut self, _source_span: &SpanWithSource) {}
fn should_halt(&self) -> bool {
self.acc > self.max
}
fn characters_on_current_line(&self) -> u32 {
0
}
fn is_counting(&self) -> bool {
true
}
}
#[cfg(test)]
mod to_string_tests {
use super::*;
fn serializer<T: ToString>(t: &mut T) {
t.push_str("Hello");
t.push(' ');
t.push_str("World");
}
#[test]
fn string_concatenation() {
let mut s = String::new();
serializer(&mut s);
assert_eq!(&s, "Hello World");
}
#[test]
fn counting() {
let mut s = Counter::new(usize::MAX);
serializer(&mut s);
assert_eq!(s.get_count(), "Hello World".chars().count());
}
#[test]
fn max_counter() {
let mut s = Counter::new(14);
serializer(&mut s);
assert!(!s.should_halt());
serializer(&mut s);
assert!(s.should_halt());
}
}