From e74c6438161ae893d7c470eb6800e724672ebb48 Mon Sep 17 00:00:00 2001 From: piyoppi Date: Sun, 10 Nov 2024 00:27:50 +0900 Subject: [PATCH] add list option --- src/chiritori.rs | 80 +++++++++++++++++---------- src/code.rs | 1 + src/code/list.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++ src/code/remover.rs | 2 +- src/main.rs | 20 ++++--- 5 files changed, 196 insertions(+), 36 deletions(-) create mode 100644 src/code/list.rs diff --git a/src/chiritori.rs b/src/chiritori.rs index 6ca9c9d..315c5be 100644 --- a/src/chiritori.rs +++ b/src/chiritori.rs @@ -1,6 +1,7 @@ use crate::{ code::{ formatter::{self, BlockFormatter, Formatter}, + list::build_list, remover::{ self, marker::{ @@ -26,8 +27,6 @@ use std::{ }; pub struct ChiritoriConfiguration { - pub delimiter_start: String, - pub delimiter_end: String, pub time_limited_configuration: TimeLimitedConfiguration, pub marker_tag_configuration: MarkerTagConfiguration, } @@ -43,12 +42,44 @@ pub struct MarkerTagConfiguration { pub marker_removal_tags: HashSet, } -pub fn clean(content: Rc, config: ChiritoriConfiguration) -> String { - let tokens = tokenizer::tokenize(&content, &config.delimiter_start, &config.delimiter_end); +pub fn clean( + content: Rc, + delimiters: (String, String), + config: ChiritoriConfiguration, +) -> String { + let (delimiter_start, delimiter_end) = delimiters; + let tokens = tokenizer::tokenize(&content, &delimiter_start, &delimiter_end); let parsed = parser::parse(&tokens); - let mut builder_map: HashMap> = HashMap::new(); + let remover = build_remover(config, content.clone()); + let (removed, markers) = remover.remove(parsed, &content); + + let removed_pos = remover::get_removed_pos(&markers); + let formatter = build_formatters(); + let structure_formatters: Vec> = vec![Box::new( + formatter::block_indent_remover::BlockIndentRemover {}, + )]; + + formatter::format(&removed, &removed_pos, &formatter, &structure_formatters) +} + +pub fn list( + content: Rc, + delimiters: (String, String), + config: ChiritoriConfiguration, +) -> String { + let (delimiter_start, delimiter_end) = delimiters; + let tokens = tokenizer::tokenize(&content, &delimiter_start, &delimiter_end); + + let parsed = parser::parse(&tokens); + let remover = build_remover(config, content.clone()); + let (_, markers) = remover.remove(parsed, &content); + build_list(&content, &markers) +} + +fn build_remover(config: ChiritoriConfiguration, content: Rc) -> Remover { + let mut builder_map: HashMap> = HashMap::new(); builder_map.insert( config.time_limited_configuration.tag_name, Box::new( @@ -71,9 +102,7 @@ pub fn clean(content: Rc, config: ChiritoriConfiguration) -> String { let remove_strategy_map: RemoveStrategies = vec![ ( Box::new(UnwrapBlockMarkerAvailability::new("unwrap-block")), - Box::new(UnwrapBlockMarkerBuilder { - content: Rc::clone(&content), - }), + Box::new(UnwrapBlockMarkerBuilder { content }), ), ( Box::new(RangeMarkerAvailability::default()), @@ -81,21 +110,16 @@ pub fn clean(content: Rc, config: ChiritoriConfiguration) -> String { ), ]; - let remover = Remover::new(builder_map, remove_strategy_map); - let (removed, markers) = remover.remove(parsed, &content); + Remover::new(builder_map, remove_strategy_map) +} - let removed_pos = remover::get_removed_pos(&markers); - let formatter: Vec> = vec![ +fn build_formatters() -> Vec> { + vec![ Box::new(formatter::indent_remover::IndentRemover {}), Box::new(formatter::empty_line_remover::EmptyLineRemover {}), Box::new(formatter::prev_line_break_remover::PrevLineBreakRemover {}), Box::new(formatter::next_line_break_remover::NextLineBreakRemover {}), - ]; - let structure_formatters: Vec> = vec![Box::new( - formatter::block_indent_remover::BlockIndentRemover {}, - )]; - - formatter::format(&removed, &removed_pos, &formatter, &structure_formatters) + ] } #[cfg(test)] @@ -107,10 +131,8 @@ mod tests { use std::io::prelude::*; use std::{fs::File, path::PathBuf}; - fn create_test_config(delimiter_start: &str, delimiter_end: &str) -> ChiritoriConfiguration { + fn create_test_config() -> ChiritoriConfiguration { ChiritoriConfiguration { - delimiter_start: String::from(delimiter_start), - delimiter_end: String::from(delimiter_end), time_limited_configuration: TimeLimitedConfiguration { tag_name: String::from("time-limited"), current: Local::now(), @@ -165,9 +187,9 @@ mod tests { "#, ); - - let config = create_test_config(""); - let result = clean(content.into(), config); + let config = create_test_config(); + let delimiters = (String::from("")); + let result = clean(content.into(), delimiters, config); assert_eq!(result, expected); } @@ -217,8 +239,9 @@ console.log("Temporary code while feature2 is not released") "#, ); - let config = create_test_config("/* <", "> */"); - let result = clean(content.into(), config); + let config = create_test_config(); + let delimiters = (String::from("/* <"), String::from("> */")); + let result = clean(content.into(), delimiters, config); assert_eq!(result, expected); } @@ -244,8 +267,9 @@ console.log("Temporary code while feature2 is not released") .read_to_string(&mut expected_content) .expect("Failed to load an expected content file"); - let config = create_test_config("/* <", "> */"); - let result = clean(input_content.into(), config); + let config = create_test_config(); + let delimiters = (String::from("/* <"), String::from("> */")); + let result = clean(input_content.into(), delimiters, config); assert_eq!(result, expected_content); } diff --git a/src/code.rs b/src/code.rs index 65352f1..1bf2a1e 100644 --- a/src/code.rs +++ b/src/code.rs @@ -1,3 +1,4 @@ pub mod formatter; +pub mod list; pub mod remover; pub mod utils; diff --git a/src/code/list.rs b/src/code/list.rs new file mode 100644 index 0000000..0ec9dff --- /dev/null +++ b/src/code/list.rs @@ -0,0 +1,129 @@ +use super::{ + remover::RemoveMarker, + utils::line_break_pos_finder::{find_next_line_break_pos, find_prev_line_break_pos}, +}; + +const MARKER_START: &str = "\x1b[32m_start\x1b[0m"; +const MARKER_END: &str = "\x1b[32m‾end\x1b[0m"; +const START_COLOR: &str = "\x1b[31m"; +const END_COLOR: &str = "\x1b[0m"; +const MARKER_START_LEN: usize = MARKER_START.len(); +const MARKER_END_LEN: usize = MARKER_END.len(); +const START_COLOR_LEN: usize = START_COLOR.len(); +const END_COLOR_LEN: usize = END_COLOR.len(); + +const HEAD_START: &str = "\n--------[ "; +const HEAD_END: &str = " ]--------\n"; + +pub fn build_item(content: &str, start: usize, end: usize) -> String { + if end - start == 0 || content.is_empty() { + return String::new(); + } + + let bytes = content.as_bytes(); + let line_start = find_prev_line_break_pos(content, bytes, start, false) + .map(|v| v + 1) + .unwrap_or(0); + let line_end_start_pos = find_prev_line_break_pos(content, bytes, end - 1, false) + .map(|v| v + 1) + .unwrap_or(0); + let line_end = + find_next_line_break_pos(content, bytes, end - 1, false).unwrap_or(content.len()); + + let color_start = start; + // If the end position is a line break (= line_end < end), it is not included. + let color_end = end.min(line_end); + + let marker_start_ofs_len = start - line_start; + let marker_end_ofs_len = end - line_end_start_pos - 1; + let mut result = String::with_capacity( + marker_start_ofs_len + + MARKER_START_LEN + + 1 + + START_COLOR_LEN + + (line_end - line_start) + + END_COLOR_LEN + + 1 + + marker_end_ofs_len + + MARKER_END_LEN, + ); + + result.push_str(&" ".repeat(marker_start_ofs_len)); + result.push_str(MARKER_START); + result.push('\n'); + result.push_str(&content[line_start..color_start]); + result.push_str(START_COLOR); + result.push_str(&content[color_start..color_end]); + result.push_str(END_COLOR); + result.push_str(&content[color_end..line_end]); + result.push('\n'); + result.push_str(&" ".repeat(marker_end_ofs_len)); + result.push_str(MARKER_END); + + result +} + +pub fn build_list(content: &str, markers: &[RemoveMarker]) -> String { + markers + .iter() + .zip(1..=markers.len()) + .map(|((range, _), idx)| { + let mut res = String::from(HEAD_START); + res.push_str(&idx.to_string()); + res.push_str(HEAD_END); + res.push_str(&build_item(content, range.start, range.end)); + + res + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + use std::ops::Range; + + #[rstest] + // 0 10 + // 012345678901234567 + #[case("aaa+bbbb+ccc+dddd", 4..11, format!("{}{}+{}{}{}{}{}+{}{}", "", MARKER_START, "", START_COLOR, "bbbb+cc", END_COLOR, "c", " ", MARKER_END))] + #[case("aaa+bbbb+ccc+dddd", 3..11, format!("{}{}+{}{}{}{}{}+{}{}", " ", MARKER_START, "aaa", START_COLOR, "+bbbb+cc", END_COLOR, "c", " ", MARKER_END))] + #[case("aaa+bbbb+ccc+dddd", 2..11, format!("{}{}+{}{}{}{}{}+{}{}", " ", MARKER_START, "aa", START_COLOR, "a+bbbb+cc", END_COLOR, "c", " ", MARKER_END))] + #[case("aaa+bbbb+ccc+dddd", 4..12, format!("{}{}+{}{}{}{}{}+{}{}", "", MARKER_START, "", START_COLOR, "bbbb+ccc", END_COLOR, "", " ", MARKER_END))] + #[case("aaa+bbbb+ccc+dddd", 4..13, format!("{}{}+{}{}{}{}{}+{}{}", "", MARKER_START, "", START_COLOR, "bbbb+ccc", END_COLOR, "", " ", MARKER_END))] + #[case("aaa+bbbb+ccc+dddd", 4..16, format!("{}{}+{}{}{}{}{}+{}{}", "", MARKER_START, "", START_COLOR, "bbbb+ccc+ddd", END_COLOR, "d", " ", MARKER_END))] + #[case("abcd", 1..2, format!("{}{}+{}{}{}{}{}+{}{}", " ", MARKER_START, "a", START_COLOR, "b", END_COLOR, "cd", " ", MARKER_END))] + #[case("", 0..0, "")] + fn test_build_item( + #[case] content: &str, + #[case] range: Range, + #[case] expected: String, + ) { + let content = content.replace('+', "\n"); + + assert_eq!( + build_item(&content, range.start, range.end), + expected.replace('+', "\n") + ); + } + + #[test] + fn test_build_list() { + // 0123456789012345678 + let content = "aaaa+bbbb+cccc+dddd".replace('+', "\n"); + let markers = [(1..2, None), (7..12, None)]; + + let expected_item1 = build_item(&content, 1, 2); + let expected_item2 = build_item(&content, 7, 12); + + assert_eq!( + build_list(&content, &markers), + format!( + "{}1{}{}{}2{}{}", + HEAD_START, HEAD_END, expected_item1, HEAD_START, HEAD_END, expected_item2 + ) + .replace('+', "\n") + ) + } +} diff --git a/src/code/remover.rs b/src/code/remover.rs index 48c93cf..62a8dfd 100644 --- a/src/code/remover.rs +++ b/src/code/remover.rs @@ -9,7 +9,7 @@ use removal_evaluator::RemovalEvaluator; use std::collections::HashMap; use std::ops::Range; -type RemoveMarker = (Range, Option); +pub type RemoveMarker = (Range, Option); pub type RemovedMarker = (usize, Option); type RemovalEvaluators = HashMap>; diff --git a/src/main.rs b/src/main.rs index 930f0a5..1e2f65e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,10 @@ struct Args { /// Name of removal-marker to be removed #[arg(long, default_value = "vec![]")] removal_marker_target_name: Vec, + + /// List source code to be removed + #[arg(short, long)] + list: bool, } fn main() { @@ -73,8 +77,6 @@ fn main() { let marker_removal_tags = HashSet::from_iter(args.removal_marker_target_name); let config = ChiritoriConfiguration { - delimiter_start: args.delimiter_start, - delimiter_end: args.delimiter_end, time_limited_configuration: TimeLimitedConfiguration { tag_name: args.time_limited_tag_name, time_offset: args.time_limited_time_offset, @@ -89,13 +91,17 @@ fn main() { }, }; - let cleaned = chiritori::clean(content, config); + let output = if args.list { + chiritori::list(content, (args.delimiter_start, args.delimiter_end), config) + } else { + chiritori::clean(content, (args.delimiter_start, args.delimiter_end), config) + }; - if let Some(output) = args.output { - let mut f = File::create(output).expect("file not found"); - f.write_all(cleaned.as_bytes()) + if let Some(filename) = args.output { + let mut f = File::create(filename).expect("file not found"); + f.write_all(output.as_bytes()) .expect("something went wrong writing the file"); } else { - print!("{}", cleaned); + print!("{}", output); } }