use std::cmp::Ordering;
use std::ops::{Deref, Range};
use std::sync::Arc;
use smol_str::SmolStr;
use crate::cli::formatters::OutputStreamFormatter;
use crate::core::config::FluffConfig;
use crate::core::errors::{SQLFluffSkipFile, SQLFluffUserError, ValueError};
use crate::core::slice_helpers::zero_slice;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TemplatedFileSlice {
pub slice_type: String,
pub source_slice: Range<usize>,
pub templated_slice: Range<usize>,
}
impl TemplatedFileSlice {
pub fn new(
slice_type: &str,
source_slice: Range<usize>,
templated_slice: Range<usize>,
) -> Self {
Self { slice_type: slice_type.to_string(), source_slice, templated_slice }
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
pub struct TemplatedFile {
inner: Arc<TemplatedFileInner>,
}
impl TemplatedFile {
pub fn new(
source_str: String,
f_name: String,
input_templated_str: Option<String>,
sliced_file: Option<Vec<TemplatedFileSlice>>,
input_raw_sliced: Option<Vec<RawFileSlice>>,
) -> Result<TemplatedFile, SQLFluffSkipFile> {
Ok(TemplatedFile {
inner: Arc::new(TemplatedFileInner::new(
source_str,
f_name,
input_templated_str,
sliced_file,
input_raw_sliced,
)?),
})
}
pub fn from_string(raw: String) -> TemplatedFile {
TemplatedFile {
inner: Arc::new(
TemplatedFileInner::new(raw.clone(), "<string>".to_string(), None, None, None)
.unwrap(),
),
}
}
}
impl Deref for TemplatedFile {
type Target = TemplatedFileInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
pub struct TemplatedFileInner {
pub source_str: String,
f_name: String,
pub templated_str: Option<String>,
source_newlines: Vec<usize>,
templated_newlines: Vec<usize>,
raw_sliced: Vec<RawFileSlice>,
pub sliced_file: Vec<TemplatedFileSlice>,
}
impl TemplatedFileInner {
pub fn new(
source_str: String,
f_name: String,
input_templated_str: Option<String>,
sliced_file: Option<Vec<TemplatedFileSlice>>,
input_raw_sliced: Option<Vec<RawFileSlice>>,
) -> Result<TemplatedFileInner, SQLFluffSkipFile> {
let templated_str = input_templated_str.clone().unwrap_or(source_str.clone());
let (sliced_file, raw_sliced): (Vec<TemplatedFileSlice>, Vec<RawFileSlice>) =
match sliced_file {
None => {
if templated_str != source_str {
panic!("Cannot instantiate a templated file unsliced!")
} else if input_raw_sliced.is_some() {
panic!("Templated file was not sliced, but not has raw slices.")
} else {
(
vec![TemplatedFileSlice::new(
"literal",
0..source_str.len(),
0..source_str.len(),
)],
vec![RawFileSlice::new(
source_str.clone(),
"literal".to_string(),
0,
None,
None,
)],
)
}
}
Some(sliced_file) => {
if let Some(raw_sliced) = input_raw_sliced {
(sliced_file, raw_sliced)
} else {
panic!("Templated file was sliced, but not raw.")
}
}
};
let source_newlines: Vec<usize> = iter_indices_of_newlines(source_str.as_str()).collect();
let templated_newlines: Vec<usize> =
iter_indices_of_newlines(templated_str.as_str()).collect();
let mut pos = 0;
for rfs in &raw_sliced {
if rfs.source_idx != pos {
panic!(
"TemplatedFile. Consistency fail on running source length. {} != {}",
pos, rfs.source_idx
)
}
pos += rfs.raw.len();
}
if pos != source_str.len() {
panic!(
"TemplatedFile. Consistency fail on final source length. {} != {}",
pos,
source_str.len()
)
}
let mut previous_slice: Option<&TemplatedFileSlice> = None;
let mut outer_tfs: Option<&TemplatedFileSlice> = None;
for tfs in &sliced_file {
match &previous_slice {
Some(previous_slice) => {
if tfs.templated_slice.start != previous_slice.templated_slice.end {
return Err(SQLFluffSkipFile::new(
"Templated slices found to be non-contiguous.".to_string(),
));
}
}
None => {
if tfs.templated_slice.start != 0 {
return Err(SQLFluffSkipFile::new(format!(
"First templated slice does not start at 0, (found slice {:?})",
tfs.templated_slice
)));
}
}
}
previous_slice = Some(tfs);
outer_tfs = Some(tfs)
}
if !sliced_file.is_empty() && input_templated_str.is_some() {
if let Some(outer_tfs) = outer_tfs {
if outer_tfs.templated_slice.end != templated_str.len() {
return Err(SQLFluffSkipFile::new(format!(
"Last templated slice does not end at end of string, (found slice {:?})",
outer_tfs.templated_slice
)));
}
}
}
Ok(TemplatedFileInner {
raw_sliced,
source_newlines,
templated_newlines,
source_str: source_str.clone(),
sliced_file,
f_name,
templated_str: Some(templated_str),
})
}
pub fn is_templated(&self) -> bool {
self.templated_str.is_some()
}
pub fn get_line_pos_of_char_pos(&self, char_pos: usize, source: bool) -> (usize, usize) {
let ref_str = if source { &self.source_newlines } else { &self.templated_newlines };
match ref_str.binary_search(&char_pos) {
Ok(nl_idx) | Err(nl_idx) => {
if nl_idx > 0 {
(nl_idx + 1, char_pos - ref_str[nl_idx - 1])
} else {
(1, char_pos + 1)
}
}
}
}
pub fn from_string(raw: SmolStr) -> TemplatedFile {
TemplatedFile::new(raw.into(), "<string>".to_string(), None, None, None).unwrap()
}
pub fn get_templated_string(&self) -> Option<&str> {
self.templated_str.as_deref()
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.templated_str.clone().unwrap().to_string()
}
pub fn source_only_slices(&self) -> Vec<RawFileSlice> {
let mut ret_buff = vec![];
for element in &self.raw_sliced {
if element.is_source_only_slice() {
ret_buff.push(element.clone());
}
}
ret_buff
}
pub fn find_slice_indices_of_templated_pos(
&self,
templated_pos: usize,
start_idx: Option<usize>,
inclusive: Option<bool>,
) -> Result<(usize, usize), ValueError> {
let start_idx = start_idx.unwrap_or(0);
let inclusive = inclusive.unwrap_or(true);
let mut first_idx: Option<usize> = None;
let mut last_idx = start_idx;
for (idx, elem) in self.sliced_file[start_idx..self.sliced_file.len()].iter().enumerate() {
last_idx = idx + start_idx;
if elem.templated_slice.end >= templated_pos {
if first_idx.is_none() {
first_idx = Some(idx + start_idx);
}
#[allow(clippy::if_same_then_else)]
if elem.templated_slice.start > templated_pos {
break;
} else if !inclusive && elem.templated_slice.end >= templated_pos {
break;
}
}
}
if last_idx == self.sliced_file.len() - 1 {
last_idx += 1;
}
match first_idx {
Some(first_idx) => Ok((first_idx, last_idx)),
None => Err(ValueError::new("Position Not Found".to_string())),
}
}
pub fn templated_slice_to_source_slice(
&self,
template_slice: Range<usize>,
) -> Result<Range<usize>, ValueError> {
if self.sliced_file.is_empty() {
return Ok(template_slice);
}
let sliced_file = self.sliced_file.clone();
let (ts_start_sf_start, ts_start_sf_stop) =
self.find_slice_indices_of_templated_pos(template_slice.start, None, None)?;
let ts_start_subsliced_file = &sliced_file[ts_start_sf_start..ts_start_sf_stop];
let mut insertion_point: isize = -1;
for elem in ts_start_subsliced_file.iter() {
for &slice_elem in ["start", "stop"].iter() {
let elem_val = match slice_elem {
"start" => elem.templated_slice.start,
"stop" => elem.templated_slice.end,
_ => panic!("Unexpected slice_elem"),
};
if elem_val == template_slice.start {
let point = if slice_elem == "start" {
elem.source_slice.start
} else {
elem.source_slice.end
};
let point: isize = point.try_into().unwrap();
if insertion_point < 0 || point < insertion_point {
insertion_point = point;
}
}
}
}
if template_slice.start == template_slice.end {
return if insertion_point >= 0 {
Ok(zero_slice(insertion_point.try_into().unwrap()))
} else if !ts_start_subsliced_file.is_empty()
&& ts_start_subsliced_file[0].slice_type == "literal"
{
let offset =
template_slice.start - ts_start_subsliced_file[0].templated_slice.start;
Ok(zero_slice(ts_start_subsliced_file[0].source_slice.start + offset))
} else {
Err(ValueError::new(format!(
"Attempting a single length slice within a templated section! {:?} within \
{:?}.",
template_slice, ts_start_subsliced_file
)))
};
}
let (ts_stop_sf_start, ts_stop_sf_stop) =
self.find_slice_indices_of_templated_pos(template_slice.end, None, Some(false))?;
let mut ts_start_sf_start = ts_start_sf_start;
if insertion_point >= 0 {
for elem in &sliced_file[ts_start_sf_start..] {
let insertion_point: usize = insertion_point.try_into().unwrap();
if elem.source_slice.start != insertion_point {
ts_start_sf_start += 1;
} else {
break;
}
}
}
let subslices = &sliced_file[usize::min(ts_start_sf_start, ts_stop_sf_start)
..usize::max(ts_start_sf_stop, ts_stop_sf_stop)];
let start_slices = if ts_start_sf_start == ts_start_sf_stop {
return match ts_start_sf_start.cmp(&sliced_file.len()) {
Ordering::Greater => Err(ValueError::new(
"Starting position higher than sliced file position".into(),
)),
Ordering::Less => Ok(sliced_file[1].source_slice.clone()),
Ordering::Equal => Ok(sliced_file.last().unwrap().source_slice.clone()),
};
} else {
&sliced_file[ts_start_sf_start..ts_start_sf_stop]
};
let stop_slices = if ts_stop_sf_start == ts_stop_sf_stop {
vec![sliced_file[ts_stop_sf_start].clone()]
} else {
sliced_file[ts_stop_sf_start..ts_stop_sf_stop].to_vec()
};
let source_start: isize = if insertion_point >= 0 {
insertion_point
} else if start_slices[0].slice_type == "literal" {
let offset = template_slice.start - start_slices[0].templated_slice.start;
(start_slices[0].source_slice.start + offset).try_into().unwrap()
} else {
start_slices[0].source_slice.start.try_into().unwrap()
};
let source_stop = if stop_slices.last().unwrap().slice_type == "literal" {
let offset = stop_slices.last().unwrap().templated_slice.end - template_slice.end;
stop_slices.last().unwrap().source_slice.end - offset
} else {
stop_slices.last().unwrap().source_slice.end
};
let source_slice;
if source_start > source_stop.try_into().unwrap() {
let mut source_start = usize::MAX;
let mut source_stop = 0;
for elem in subslices {
source_start = usize::min(source_start, elem.source_slice.start);
source_stop = usize::max(source_stop, elem.source_slice.end);
}
source_slice = source_start..source_stop;
} else {
source_slice = source_start.try_into().unwrap()..source_stop;
}
Ok(source_slice)
}
pub fn is_source_slice_literal(&self, source_slice: &Range<usize>) -> bool {
if self.raw_sliced.is_empty() {
return true;
};
if source_slice.start == source_slice.end {
return true;
};
let mut is_literal = true;
for raw_slice in &self.raw_sliced {
if raw_slice.source_idx <= source_slice.start {
is_literal = raw_slice.slice_type == "literal";
} else if raw_slice.source_idx >= source_slice.end {
break;
} else if raw_slice.slice_type != "literal" {
is_literal = false;
};
}
is_literal
}
#[allow(dead_code)]
fn raw_slices_spanning_source_slice(&self, source_slice: Range<usize>) -> Vec<RawFileSlice> {
let last_raw_slice = self.raw_sliced.last().unwrap();
if source_slice.start >= last_raw_slice.source_idx + last_raw_slice.raw.len() {
return Vec::new();
}
let mut raw_slice_idx = 0;
while raw_slice_idx + 1 < self.raw_sliced.len()
&& self.raw_sliced[raw_slice_idx + 1].source_idx <= source_slice.start
{
raw_slice_idx += 1;
}
let mut slice_span = 1;
while raw_slice_idx + slice_span < self.raw_sliced.len()
&& self.raw_sliced[raw_slice_idx + slice_span].source_idx < source_slice.end
{
slice_span += 1;
}
self.raw_sliced[raw_slice_idx..(raw_slice_idx + slice_span)].to_vec()
}
}
pub fn iter_indices_of_newlines(raw_str: &str) -> impl Iterator<Item = usize> + '_ {
raw_str.match_indices('\n').map(|(idx, _)| idx)
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum RawFileSliceType {
Comment,
BlockEnd,
BlockStart,
BlockMid,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct RawFileSlice {
raw: String,
slice_type: String,
pub source_idx: usize,
slice_subtype: Option<RawFileSliceType>,
block_idx: usize,
}
impl RawFileSlice {
pub fn new(
raw: String,
slice_type: String,
source_idx: usize,
slice_subtype: Option<RawFileSliceType>,
block_idx: Option<usize>,
) -> Self {
Self { raw, slice_type, source_idx, slice_subtype, block_idx: block_idx.unwrap_or(0) }
}
}
impl RawFileSlice {
fn end_source_idx(&self) -> usize {
self.source_idx + self.raw.len()
}
pub(crate) fn source_slice(&self) -> Range<usize> {
self.source_idx..self.end_source_idx()
}
fn is_source_only_slice(&self) -> bool {
matches!(self.slice_type.as_str(), "comment" | "block_end" | "block_start" | "block_mid")
}
}
pub trait Templater: Send + Sync {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn template_selection(&self) -> &str;
fn config_pairs(&self) -> (String, String);
fn sequence_files(
&self,
f_names: Vec<String>,
config: Option<&FluffConfig>,
formatter: Option<&OutputStreamFormatter>,
) -> Vec<String>;
fn process(
&self,
in_str: &str,
f_name: &str,
config: Option<&FluffConfig>,
formatter: Option<&OutputStreamFormatter>,
) -> Result<TemplatedFile, SQLFluffUserError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_indices_of_newlines() {
vec![
("", vec![]),
("foo", vec![]),
("foo\nbar", vec![3]),
("\nfoo\n\nbar\nfoo\n\nbar\n", vec![0, 4, 5, 9, 13, 14, 18]),
]
.into_iter()
.for_each(|(in_str, expected)| {
assert_eq!(expected, iter_indices_of_newlines(in_str).collect::<Vec<usize>>())
});
}
fn simple_sliced_file() -> Vec<TemplatedFileSlice> {
vec![
TemplatedFileSlice::new("literal", 0..10, 0..10),
TemplatedFileSlice::new("templated", 10..17, 10..12),
TemplatedFileSlice::new("literal", 17..25, 12..20),
]
}
fn simple_raw_sliced_file() -> [RawFileSlice; 3] {
[
RawFileSlice::new("x".repeat(10), "literal".to_string(), 0, None, None),
RawFileSlice::new("x".repeat(7), "templated".to_string(), 10, None, None),
RawFileSlice::new("x".repeat(8), "literal".to_string(), 17, None, None),
]
}
fn complex_sliced_file() -> Vec<TemplatedFileSlice> {
vec![
TemplatedFileSlice::new("literal", 0..13, 0..13),
TemplatedFileSlice::new("comment", 13..29, 13..13),
TemplatedFileSlice::new("literal", 29..44, 13..28),
TemplatedFileSlice::new("block_start", 44..68, 28..28),
TemplatedFileSlice::new("literal", 68..81, 28..41),
TemplatedFileSlice::new("templated", 81..86, 41..42),
TemplatedFileSlice::new("literal", 86..110, 42..66),
TemplatedFileSlice::new("templated", 68..86, 66..76),
TemplatedFileSlice::new("literal", 68..81, 76..89),
TemplatedFileSlice::new("templated", 81..86, 89..90),
TemplatedFileSlice::new("literal", 86..110, 90..114),
TemplatedFileSlice::new("templated", 68..86, 114..125),
TemplatedFileSlice::new("literal", 68..81, 125..138),
TemplatedFileSlice::new("templated", 81..86, 138..139),
TemplatedFileSlice::new("literal", 86..110, 139..163),
TemplatedFileSlice::new("templated", 110..123, 163..166),
TemplatedFileSlice::new("literal", 123..132, 166..175),
TemplatedFileSlice::new("block_end", 132..144, 175..175),
TemplatedFileSlice::new("literal", 144..155, 175..186),
TemplatedFileSlice::new("block_start", 155..179, 186..186),
TemplatedFileSlice::new("literal", 179..189, 186..196),
TemplatedFileSlice::new("templated", 189..194, 196..197),
TemplatedFileSlice::new("literal", 194..203, 197..206),
TemplatedFileSlice::new("literal", 179..189, 206..216),
TemplatedFileSlice::new("templated", 189..194, 216..217),
TemplatedFileSlice::new("literal", 194..203, 217..226),
TemplatedFileSlice::new("literal", 179..189, 226..236),
TemplatedFileSlice::new("templated", 189..194, 236..237),
TemplatedFileSlice::new("literal", 194..203, 237..246),
TemplatedFileSlice::new("block_end", 203..215, 246..246),
TemplatedFileSlice::new("literal", 215..230, 246..261),
]
}
fn complex_raw_sliced_file() -> Vec<RawFileSlice> {
vec![
RawFileSlice::new("x".repeat(13).to_string(), "literal".to_string(), 0, None, None),
RawFileSlice::new("x".repeat(16).to_string(), "comment".to_string(), 13, None, None),
RawFileSlice::new("x".repeat(15).to_string(), "literal".to_string(), 29, None, None),
RawFileSlice::new(
"x".repeat(24).to_string(),
"block_start".to_string(),
44,
None,
None,
),
RawFileSlice::new("x".repeat(13).to_string(), "literal".to_string(), 68, None, None),
RawFileSlice::new("x".repeat(5).to_string(), "templated".to_string(), 81, None, None),
RawFileSlice::new("x".repeat(24).to_string(), "literal".to_string(), 86, None, None),
RawFileSlice::new("x".repeat(13).to_string(), "templated".to_string(), 110, None, None),
RawFileSlice::new("x".repeat(9).to_string(), "literal".to_string(), 123, None, None),
RawFileSlice::new("x".repeat(12).to_string(), "block_end".to_string(), 132, None, None),
RawFileSlice::new("x".repeat(11).to_string(), "literal".to_string(), 144, None, None),
RawFileSlice::new(
"x".repeat(24).to_string(),
"block_start".to_string(),
155,
None,
None,
),
RawFileSlice::new("x".repeat(10).to_string(), "literal".to_string(), 179, None, None),
RawFileSlice::new("x".repeat(5).to_string(), "templated".to_string(), 189, None, None),
RawFileSlice::new("x".repeat(9).to_string(), "literal".to_string(), 194, None, None),
RawFileSlice::new("x".repeat(12).to_string(), "block_end".to_string(), 203, None, None),
RawFileSlice::new("x".repeat(15).to_string(), "literal".to_string(), 215, None, None),
]
}
struct FileKwargs {
f_name: String,
source_str: String,
templated_str: Option<String>,
sliced_file: Vec<TemplatedFileSlice>,
raw_sliced_file: Vec<RawFileSlice>,
}
fn simple_file_kwargs() -> FileKwargs {
FileKwargs {
f_name: "test.sql".to_string(),
source_str: "01234\n6789{{foo}}fo\nbarss".to_string(),
templated_str: Some("01234\n6789x\nfo\nbarss".to_string()),
sliced_file: simple_sliced_file().to_vec(),
raw_sliced_file: simple_raw_sliced_file().to_vec(),
}
}
fn complex_file_kwargs() -> FileKwargs {
FileKwargs {
f_name: "test.sql".to_string(),
source_str: complex_raw_sliced_file().iter().fold(String::new(), |acc, x| acc + &x.raw),
templated_str: None,
sliced_file: complex_sliced_file().to_vec(),
raw_sliced_file: complex_raw_sliced_file().to_vec(),
}
}
#[test]
fn test_templated_file_get_line_pos_of_char_pos() {
let tests = [
(simple_file_kwargs(), 0, 1, 1),
(simple_file_kwargs(), 20, 3, 1),
(simple_file_kwargs(), 24, 3, 5),
];
for test in tests {
let kwargs = test.0;
let tf = TemplatedFile::new(
kwargs.source_str,
kwargs.f_name,
kwargs.templated_str,
Some(kwargs.sliced_file),
Some(kwargs.raw_sliced_file),
)
.unwrap();
let (res_line_no, res_line_pos) = tf.get_line_pos_of_char_pos(test.1, true);
assert_eq!(res_line_no, test.2);
assert_eq!(res_line_pos, test.3);
}
}
#[test]
fn test_templated_file_find_slice_indices_of_templated_pos() {
let tests = vec![
(12, true, simple_file_kwargs(), 1, 3),
(20, true, simple_file_kwargs(), 2, 3),
];
for test in tests {
let args = test.2;
let file = TemplatedFile::new(
args.source_str,
args.f_name,
args.templated_str,
Some(args.sliced_file),
Some(args.raw_sliced_file),
)
.unwrap();
let (res_start, res_stop) =
file.find_slice_indices_of_templated_pos(test.0, None, Some(test.1)).unwrap();
assert_eq!(res_start, test.3);
assert_eq!(res_stop, test.4);
}
}
#[test]
fn test_templated_file_templated_slice_to_source_slice() {
let test_cases = vec![
(
5..10,
5..10,
true,
FileKwargs {
sliced_file: vec![TemplatedFileSlice::new("literal", 0..20, 0..20)],
raw_sliced_file: vec![RawFileSlice::new(
"x".repeat(20),
"literal".to_string(),
0,
None,
None,
)],
source_str: "x".repeat(20),
f_name: "foo.sql".to_string(),
templated_str: None,
},
),
(10..13, 10..13, true, complex_file_kwargs()),
(
5..10,
55..60,
true,
FileKwargs {
sliced_file: vec![TemplatedFileSlice::new("literal", 50..70, 0..20)],
raw_sliced_file: vec![
RawFileSlice::new("x".repeat(50), "literal".to_string(), 0, None, None),
RawFileSlice::new("x".repeat(20), "literal".to_string(), 50, None, None),
],
source_str: "x".repeat(70),
f_name: "foo.sql".to_string(),
templated_str: None,
},
),
(5..15, 5..20, false, simple_file_kwargs()),
(
5..15,
0..25,
false,
FileKwargs {
sliced_file: simple_file_kwargs()
.sliced_file
.iter()
.map(|slc| {
TemplatedFileSlice::new(
"templated",
slc.source_slice.clone(),
slc.templated_slice.clone(),
)
})
.collect(),
raw_sliced_file: simple_file_kwargs()
.raw_sliced_file
.iter()
.map(|slc| {
RawFileSlice::new(
slc.raw.to_string(),
"templated".to_string(),
slc.source_idx,
None,
None,
)
})
.collect(),
..simple_file_kwargs()
},
),
(10..10, 10..10, true, simple_file_kwargs()),
(12..12, 17..17, true, simple_file_kwargs()),
(
20..20,
25..25,
true,
FileKwargs {
sliced_file: simple_file_kwargs()
.sliced_file
.into_iter()
.chain(vec![TemplatedFileSlice::new("comment", 25..35, 20..20)])
.collect(),
raw_sliced_file: simple_file_kwargs()
.raw_sliced_file
.into_iter()
.chain(vec![RawFileSlice::new(
"x".repeat(10),
"comment".to_string(),
25,
None,
None,
)])
.collect(),
source_str: simple_file_kwargs().source_str.to_string() + &"x".repeat(10),
..simple_file_kwargs()
},
),
(43..43, 87..87, true, complex_file_kwargs()),
(13..13, 13..13, true, complex_file_kwargs()),
(186..186, 155..155, true, complex_file_kwargs()),
(
100..130,
68..110,
false,
complex_file_kwargs(),
),
];
for (in_slice, out_slice, is_literal, tf_kwargs) in test_cases {
let file = TemplatedFile::new(
tf_kwargs.source_str,
tf_kwargs.f_name,
tf_kwargs.templated_str,
Some(tf_kwargs.sliced_file),
Some(tf_kwargs.raw_sliced_file),
)
.unwrap();
let source_slice = file.templated_slice_to_source_slice(in_slice).unwrap();
let literal_test = file.is_source_slice_literal(&source_slice);
assert_eq!((is_literal, source_slice), (literal_test, out_slice));
}
}
#[test]
fn test_templated_file_source_only_slices() {
let test_cases = vec![
(
TemplatedFile::new(
format!("{}{}{}", "a".repeat(10), "{# b #}", "a".repeat(10)),
"test".to_string(),
None,
Some(vec![
TemplatedFileSlice::new("literal", 0..10, 0..10),
TemplatedFileSlice::new("templated", 10..17, 10..10),
TemplatedFileSlice::new("literal", 17..27, 10..20),
]),
Some(vec![
RawFileSlice::new(
"a".repeat(10).to_string(),
"literal".to_string(),
0,
None,
None,
),
RawFileSlice::new(
"{# b #}".to_string(),
"comment".to_string(),
10,
None,
None,
),
RawFileSlice::new(
"a".repeat(10).to_string(),
"literal".to_string(),
17,
None,
None,
),
]),
)
.unwrap(),
vec![RawFileSlice::new(
"{# b #}".to_string(),
"comment".to_string(),
10,
None,
None,
)],
),
(
TemplatedFile::new(
"aaa{{ b }}aaa".to_string(),
"test".to_string(),
None,
Some(vec![
TemplatedFileSlice::new("literal", 0..3, 0..3),
TemplatedFileSlice::new("templated", 3..10, 3..6),
TemplatedFileSlice::new("literal", 10..13, 6..9),
]),
Some(vec![
RawFileSlice::new("aaa".to_string(), "literal".to_string(), 0, None, None),
RawFileSlice::new(
"{{ b }}".to_string(),
"templated".to_string(),
3,
None,
None,
),
RawFileSlice::new("aaa".to_string(), "literal".to_string(), 10, None, None),
]),
)
.unwrap(),
vec![],
),
];
for (file, expected) in test_cases {
assert_eq!(file.source_only_slices(), expected, "Failed for {:?}", file);
}
}
}