#[cfg(test)]
mod tests {
use crate::scfmt;
use crate::scfmt::ScfmtErr;
use std::fs;
#[test]
fn format_str() {
let to_format = fs::read_to_string("./test_resources/1_test.rs").unwrap();
let answer = fs::read_to_string("./test_resources/1_answer.rs").unwrap();
let formatted = scfmt::format_str(&to_format, "rs").unwrap();
assert_eq!(answer, formatted);
}
#[test]
fn add_brackets() {
let to_format = fs::read_to_string("./test_resources/2_test.rs").unwrap();
let answer = fs::read_to_string("./test_resources/2_answer.rs").unwrap();
let formatted = scfmt::add_brackets(&to_format, "rs").unwrap();
assert_eq!(answer, formatted);
}
#[test]
fn remove_brackets() {
let to_format = fs::read_to_string("./test_resources/3_test.rs").unwrap();
let answer = fs::read_to_string("./test_resources/3_answer.rs").unwrap();
let formatted = scfmt::remove_brackets(&to_format, "rs").unwrap();
assert_eq!(answer, formatted);
}
#[test]
fn no_change_without_brackets() {
let before_formatting = fs::read_to_string("./test_resources/4_test.rs").unwrap();
let formatted = scfmt::format_str(&before_formatting, "rs").unwrap();
assert_eq!(formatted, before_formatting);
}
#[test]
fn no_head_for_closing() {
let before_formatting = fs::read_to_string("./test_resources/5_test.rs").unwrap();
let formatted = scfmt::format_str(&before_formatting, "rs");
assert_eq!(formatted, Err(ScfmtErr::CommentClosedNothing(46)));
}
#[test]
fn no_head_for_middle() {
let before_formatting = fs::read_to_string("./test_resources/6_test.rs").unwrap();
let formatted = scfmt::format_str(&before_formatting, "rs");
assert_eq!(formatted, Err(ScfmtErr::CommentClosedNothing(21)));
}
#[test]
fn head_never_closed() {
let before_formatting = fs::read_to_string("./test_resources/7_test.rs").unwrap();
let formatted = scfmt::format_str(&before_formatting, "rs");
assert_eq!(formatted, Err(ScfmtErr::CommentNeverClosed(1)));
}
#[test]
fn format_preserves_ending_empty_lines() {
let formatted = scfmt::format_str("", "rs").unwrap();
assert_eq!(formatted, "");
let formatted = scfmt::format_str("//>\n//<", "rs").unwrap();
assert_eq!(formatted, "//>\n//<");
let formatted = scfmt::format_str("//>\n//<\n", "rs").unwrap();
assert_eq!(formatted, "//>\n//<\n");
let formatted = scfmt::format_str("//>\n//<\n\n", "rs").unwrap();
assert_eq!(formatted, "//>\n//<\n\n");
let formatted = scfmt::format_str("//>\n//<\n\n\n", "rs").unwrap();
assert_eq!(formatted, "//>\n//<\n\n\n");
let formatted = scfmt::format_str("//>\n//<\n\n\n ", "rs").unwrap();
assert_eq!(formatted, "//>\n//<\n\n\n");
}
#[test]
fn remove_brackets_preserves_ending_empty_lines() {
let formatted = scfmt::remove_brackets("", "rs").unwrap();
assert_eq!(formatted, "");
let formatted = scfmt::remove_brackets("//>\n let a = 0;\n//<", "rs").unwrap();
assert_eq!(formatted, "//\n let a = 0;");
let formatted = scfmt::remove_brackets("//>\n let a = 0;\n//<\n", "rs").unwrap();
assert_eq!(formatted, "//\n let a = 0;\n");
let formatted = scfmt::remove_brackets("//>\n let a = 0;\n//<\n\n", "rs").unwrap();
assert_eq!(formatted, "//\n let a = 0;\n\n");
let formatted = scfmt::remove_brackets("//>\n let a = 0;\n//<\n\n\n", "rs").unwrap();
assert_eq!(formatted, "//\n let a = 0;\n\n\n");
let formatted = scfmt::remove_brackets("//>\n let a = 0;\n//<\n\n\n ", "rs").unwrap();
assert_eq!(formatted, "//\n let a = 0;\n\n\n");
}
#[test]
fn add_brackets_preserves_ending_empty_lines() {
let formatted = scfmt::add_brackets("", "rs").unwrap();
assert_eq!(formatted, "");
let formatted = scfmt::add_brackets("//\n let a = 0;", "rs").unwrap();
assert_eq!(formatted, "//>\n let a = 0;\n//<");
let formatted = scfmt::add_brackets("//\n let a = 0;\n", "rs").unwrap();
assert_eq!(formatted, "//>\n let a = 0;\n//<\n");
let formatted = scfmt::add_brackets("//\n let a = 0;\n\n", "rs").unwrap();
assert_eq!(formatted, "//>\n let a = 0;\n//<\n\n");
let formatted = scfmt::add_brackets("//\n let a = 0;\n\n\n", "rs").unwrap();
assert_eq!(formatted, "//>\n let a = 0;\n//<\n\n\n");
let formatted = scfmt::add_brackets("//\n let a = 0;\n\n\n ", "rs").unwrap();
assert_eq!(formatted, "//>\n let a = 0;\n//<\n\n\n");
}
#[test]
fn null_brackets_preserves_ending_empty_lines() {
let formatted = scfmt::null_existing_brackets("", "rs").unwrap();
assert_eq!(formatted, "");
let formatted = scfmt::null_existing_brackets("//>\n//<", "rs").unwrap();
assert_eq!(formatted, "//_>\n//_<");
let formatted = scfmt::null_existing_brackets("//>\n//<\n", "rs").unwrap();
assert_eq!(formatted, "//_>\n//_<\n");
let formatted = scfmt::null_existing_brackets("//>\n//<\n\n", "rs").unwrap();
assert_eq!(formatted, "//_>\n//_<\n\n");
let formatted = scfmt::null_existing_brackets("//>\n//<\n\n\n", "rs").unwrap();
assert_eq!(formatted, "//_>\n//_<\n\n\n");
let formatted = scfmt::null_existing_brackets("//>\n//<\n\n\n ", "rs").unwrap();
assert_eq!(formatted, "//_>\n//_<\n\n\n");
}
#[test]
fn format_str_tabs() {
let to_format = fs::read_to_string("./test_resources/9_test.rs").unwrap();
let answer = fs::read_to_string("./test_resources/9_answer.rs").unwrap();
let formatted = scfmt::format_str(&to_format, "rs").unwrap();
assert_eq!(answer, formatted);
}
#[test]
fn format_str_tab_spaces_of_2() {
let to_format = fs::read_to_string("./test_resources/10_test.rs").unwrap();
let answer = fs::read_to_string("./test_resources/10_answer.rs").unwrap();
let formatted = scfmt::format_str(&to_format, "rs").unwrap();
assert_eq!(answer, formatted);
}
#[test]
fn preserve_closing_comment_content() {
let formatted = scfmt::format_str("//>\n//< test", "rs").unwrap();
assert_eq!(formatted, "//>\n//<\n// test");
}
#[test]
fn preserve_closing_comment_content_and_spacing() {
let formatted = scfmt::format_str("//>\n// < test", "rs").unwrap();
assert_eq!(formatted, "//>\n// <\n// test");
}
#[test]
fn nullify_brackets() {
let formatted =
scfmt::null_existing_brackets("//>\n //>\n//\n //<\n//<", "rs").unwrap();
assert_eq!(formatted, "//_>\n //_>\n//\n //_<\n//_<");
}
#[test]
fn incompatible_file_type() {
let result = scfmt::format_str("", "");
assert_eq!(result, Err(ScfmtErr::IncompatibleFileType));
let result = scfmt::add_brackets("", "");
assert_eq!(result, Err(ScfmtErr::IncompatibleFileType));
let result = scfmt::remove_brackets("", "");
assert_eq!(result, Err(ScfmtErr::IncompatibleFileType));
let result = scfmt::null_existing_brackets("", "");
assert_eq!(result, Err(ScfmtErr::IncompatibleFileType));
}
#[test]
fn determine_whitespace_type_gets_best_result() {
let to_format = fs::read_to_string("./test_resources/11_test.rs").unwrap();
let formatted = scfmt::format_str(&to_format, "rs").unwrap();
let answer = fs::read_to_string("./test_resources/11_answer.rs").unwrap();
assert_eq!(answer, formatted);
}
}
pub mod scfmt {
use glob::{glob, GlobError};
use phf::phf_map;
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
static EXTENSION_TO_COMMENT_STARTER_MAP: phf::Map<&'static str, &'static str> = phf_map! {
"adb" => "--",
"ads" => "--",
"asm" => ";",
"al" => "//",
"bib" => "%",
"brs" => "'",
"c" => "//",
"cfc" => "//",
"clj" => ";",
"cls" => "//",
"cpp" => "//",
"cs" => "//",
"csx" => "//",
"d" => "//",
"dart" => "//",
"do" => "*",
"ex" => "#",
"elm" => "--",
"gd" => "#",
"gen" => "\\",
"go" => "//",
"graphql" => "#",
"groovy" => "//",
"h" => "//",
"hs" => "--",
"lhs" => "--",
"java" => "//",
"js" => "//",
"cjs" => "//",
"mjs" => "//",
"jsonc" => "//",
"lisp" => ";;",
"lua" => "--",
"m" => "%",
"nim" => "#",
"pas" => "//",
"php" => "//",
"pig" => "--",
"plsql" => "--",
"pp" => "//",
"ps1" => "#",
"pu" => "'",
"q" => "--",
"rkt" => ";",
"rs" => "//",
"sas" => "*",
"sass" => "//",
"scss" => "//",
"shader" => "//",
"sh" => "#",
"sol" => "//",
"styl" => "//",
"svelte" => "//",
"tcl" => "#",
"toml" => "#",
"ts" => "//",
"tsx" => "//",
"vala" => "//",
"v" => "//",
"vhdl" => "--",
"vue" => "//",
"yaml" => "#",
};
#[derive(PartialEq, Debug)]
pub enum ScfmtErr {
IncompatibleFileType,
CommentClosedNothing(usize),
CommentNeverClosed(usize),
CantConvertOsString,
CantReadFileAsString,
CantCreatFile,
CantWriteToFile,
}
fn determine_whitespace_type(str: &str) -> (char, usize) {
let mut chr = ' ';
let mut num = 4;
let mut tab_count = 0;
let mut space_count = 0;
let mut tab_spaces_count_map: HashMap<usize, usize> = HashMap::new();
let mut last_depth;
let mut last_diff = 0;
let mut cur_depth = 0;
for line in str.lines() {
if let Some(first_char) = line.chars().next() {
last_depth = cur_depth;
cur_depth =
if let Some((local_depth, _)) = count_and_remove_begining_whitespace(line) {
local_depth
} else {
0
};
let diff = (last_depth as isize - cur_depth as isize).abs() as usize;
match first_char {
' ' => space_count += 1,
'\t' => tab_count += 1,
_ => {}
}
if diff != 0 {
match tab_spaces_count_map.get(&diff) {
Some(x) => {
let current_map_value = x.clone();
tab_spaces_count_map.insert(diff, current_map_value + 1)
}
None => tab_spaces_count_map.insert(diff, 1),
};
last_diff = diff;
} else {
if last_diff != 0 {
match tab_spaces_count_map.get(&last_diff) {
Some(x) => {
let current_map_value = x.clone();
tab_spaces_count_map.insert(last_diff, current_map_value + 1)
}
None => tab_spaces_count_map.insert(last_diff, 1),
};
}
}
}
}
let mut highest_count = 0;
let mut diff_with_highest_count = 0;
for (diff_size, diff_count) in &tab_spaces_count_map {
if diff_count > &highest_count {
highest_count = *diff_count;
diff_with_highest_count = *diff_size;
}
}
if diff_with_highest_count != 0 {
num = diff_with_highest_count;
if tab_count > space_count {
chr = '\t'
}
}
(chr, num)
}
fn add_whitespace(line: &str, depth: usize, whitespace_char: char) -> String {
let mut value = String::from("");
for _i in 0..depth {
value.push(whitespace_char);
}
value + line
}
fn set_whitespace(str: &str, depth: usize, whitespace_char: char) -> String {
let str_no_whitespace = match count_and_remove_begining_whitespace(str) {
Some(x) => x.1,
None => "".to_owned(),
};
let mut whitespace = String::from("");
for _i in 0..depth {
whitespace.push(whitespace_char);
}
whitespace + &str_no_whitespace
}
pub fn get_files_in_dir(path: &str, filetype: &str) -> Result<Vec<PathBuf>, GlobError> {
let mut paths = Vec::new();
let mut potential_slash = "";
if PathBuf::from(path).is_dir() && !path.ends_with('/') {
potential_slash = "/";
}
let search_params = String::from(path) + potential_slash + "**/*" + filetype;
for entry in glob(&search_params).expect("Failed to read glob pattern") {
match entry {
Ok(path) => {
paths.push(path);
}
Err(e) => return Err(e),
}
}
let paths = paths.into_iter().filter(|e| e.is_file());
let paths: Vec<PathBuf> = paths
.into_iter()
.filter(|e| fs::read_to_string(e).is_ok())
.collect();
Ok(paths)
}
fn ensure_previous_lines_have_correct_indentation(
formatted_lines: &mut [String],
comment_tracker: &mut [CommentDetail],
tab_spaces: usize,
whitespace_char: char,
) {
let mut lowest_depth = comment_tracker[comment_tracker.len() - 1].depth + tab_spaces;
let line_of_last_unclosed_comment = comment_tracker[comment_tracker.len() - 1].line;
for i in line_of_last_unclosed_comment + 1..formatted_lines.len() {
let whitespaces_option = count_and_remove_begining_whitespace(&formatted_lines[i]);
match whitespaces_option {
Some(spaces_tuple) => {
if spaces_tuple.0 < lowest_depth {
lowest_depth = spaces_tuple.0;
}
}
None => continue,
}
}
if lowest_depth < comment_tracker[comment_tracker.len() - 1].depth + tab_spaces {
let depth_difference =
comment_tracker[comment_tracker.len() - 1].depth + tab_spaces - lowest_depth;
if depth_difference > 0 {
for i in line_of_last_unclosed_comment + 1..formatted_lines.len() {
match count_and_remove_begining_whitespace(&formatted_lines[i]) {
Some(_) => {
formatted_lines[i] = add_whitespace(
&formatted_lines[i],
depth_difference,
whitespace_char,
)
}
None => formatted_lines[i] = "\n".to_owned(),
}
}
}
}
}
fn chop_off_beginning_spaces(line: &str) -> (Option<usize>, &str) {
let mut line_no_leading_spaces = "";
let mut leading_spaces: Option<usize> = None;
for (i, char) in line.chars().enumerate() {
if char as u32 > 32 {
line_no_leading_spaces = &line[i..];
leading_spaces = Some(i);
break;
}
}
(leading_spaces, line_no_leading_spaces)
}
fn remove_comment_notation_if_it_exists(
line: &str,
comment_starter: &str,
) -> (bool, bool, String) {
let mut line_no_comment_starter = line;
let comment_starter_with_space = comment_starter.to_owned() + " ";
let mut is_a_comment = false;
let mut space_after_comment_starter = false;
if line_no_comment_starter.starts_with(&comment_starter_with_space) {
is_a_comment = true;
space_after_comment_starter = true;
line_no_comment_starter = &line_no_comment_starter[comment_starter.len() + 1..];
} else if line_no_comment_starter.starts_with(comment_starter) {
is_a_comment = true;
space_after_comment_starter = false;
line_no_comment_starter = &line_no_comment_starter[comment_starter.len()..];
}
(
is_a_comment,
space_after_comment_starter,
line_no_comment_starter.to_owned(),
)
}
pub fn format_str(str: &str, filetype: &str) -> Result<String, ScfmtErr> {
let comment_starter = match EXTENSION_TO_COMMENT_STARTER_MAP.get(filetype) {
Some(x) => *x,
None => return Err(ScfmtErr::IncompatibleFileType),
};
let mut formatted_file = String::from("");
let mut formatted_lines: Vec<String> = Vec::new();
let (whitespace_char, tab_spaces) = determine_whitespace_type(str);
let mut comment_tracker: Vec<CommentDetail> = Vec::new();
for (i, line) in str.lines().enumerate() {
let (leading_spaces, line_no_leading_spaces) = chop_off_beginning_spaces(line);
let (is_a_comment, space_after_comment_starter, line_no_comment_starter) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
if is_a_comment & line_no_comment_starter.starts_with('>') {
formatted_lines.push(line.to_string() + "\n");
let comment = CommentDetail {
line: i,
depth: leading_spaces.unwrap(),
};
comment_tracker.push(comment);
} else if is_a_comment & line_no_comment_starter.starts_with("<>") {
if comment_tracker.is_empty() {
return Err(ScfmtErr::CommentClosedNothing(i + 1));
}
ensure_previous_lines_have_correct_indentation(
&mut formatted_lines,
&mut comment_tracker,
tab_spaces,
whitespace_char,
);
formatted_lines.push(
set_whitespace(
line,
comment_tracker[comment_tracker.len() - 1].depth,
whitespace_char,
) + "\n",
);
let comment = CommentDetail {
line: i,
depth: comment_tracker[comment_tracker.len() - 1].depth,
};
comment_tracker.pop();
comment_tracker.push(comment);
} else if is_a_comment & line_no_comment_starter.starts_with('<') {
if comment_tracker.is_empty() {
return Err(ScfmtErr::CommentClosedNothing(i + 1));
}
ensure_previous_lines_have_correct_indentation(
&mut formatted_lines,
&mut comment_tracker,
tab_spaces,
whitespace_char,
);
let possible_space = if space_after_comment_starter { " " } else { "" };
formatted_lines.push(set_whitespace(
&(comment_starter.to_owned() + possible_space + "<\n"),
comment_tracker[comment_tracker.len() - 1].depth,
whitespace_char,
));
let comment_contents = &line_no_comment_starter[1..];
if !line_is_only_whitepace(comment_contents) {
formatted_lines.push(set_whitespace(
&(comment_starter.to_owned() + comment_contents + "\n"),
comment_tracker[comment_tracker.len() - 1].depth,
whitespace_char,
));
}
comment_tracker.pop();
} else if leading_spaces != None {
formatted_lines.push(line.to_string() + "\n");
} else {
formatted_lines.push("\n".to_string());
}
}
for line in formatted_lines {
formatted_file.push_str(&line);
}
if let Some(last_char) = str.chars().last() {
if last_char != '\n' {
formatted_file.pop();
}
}
if !comment_tracker.is_empty() {
let err_line = comment_tracker[comment_tracker.len() - 1].line + 1;
return Err(ScfmtErr::CommentNeverClosed(err_line));
}
Ok(formatted_file)
}
pub fn format_file(file: PathBuf) -> Result<(), ScfmtErr> {
let extenstion = match file.extension() {
Some(x) => match x.to_str() {
Some(x) => x,
None => return Err(ScfmtErr::CantConvertOsString),
},
None => return Err(ScfmtErr::IncompatibleFileType),
};
let contents = match fs::read_to_string(&file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantReadFileAsString),
};
let converted = format_str(&contents, extenstion)?;
if converted != contents {
let mut output = match File::create(file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantCreatFile),
};
match write!(output, "{}", converted) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantWriteToFile),
};
}
Ok(())
}
pub fn add_brackets_file(file: PathBuf) -> Result<(), ScfmtErr> {
let extenstion = match file.extension() {
Some(x) => match x.to_str() {
Some(x) => x,
None => return Err(ScfmtErr::CantConvertOsString),
},
None => return Err(ScfmtErr::IncompatibleFileType),
};
let contents = match fs::read_to_string(&file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantReadFileAsString),
};
let converted = add_brackets(&contents, extenstion)?;
if converted != contents {
let mut output = match File::create(file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantCreatFile),
};
match write!(output, "{}", converted) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantWriteToFile),
};
}
Ok(())
}
struct CommentDetail {
line: usize,
depth: usize,
}
fn make_comment_closed_and_open_bracket(line: &str, comment_starter: &str) -> Option<String> {
let (leading_spaces, line_no_leading_spaces) = chop_off_beginning_spaces(line);
let (is_a_comment, _, _line_no_comment_starter) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
if !is_a_comment {
return None;
}
let first_half = &line[..leading_spaces.unwrap() + comment_starter.len()];
let second_half = &line[leading_spaces.unwrap() + comment_starter.len()..];
Some(String::from(first_half) + "<>" + second_half)
}
fn make_comment_open_bracket(line: &str, comment_starter: &str) -> Option<String> {
let (leading_spaces, line_no_leading_spaces) = chop_off_beginning_spaces(line);
let (is_a_comment, _, _line_no_comment_starter) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
if !is_a_comment {
return None;
}
let first_half = &line[..leading_spaces.unwrap() + comment_starter.len()];
let second_half = &line[leading_spaces.unwrap() + comment_starter.len()..];
Some(String::from(first_half) + ">" + second_half)
}
fn new_comment_closed_bracket(
depth: usize,
comment_starter: &str,
whitespace_char: char,
) -> Option<String> {
let mut result = String::new();
for _i in 0..depth {
result.push(whitespace_char);
}
result.push_str(&(String::from(comment_starter) + "<"));
Some(result)
}
fn remove_empty_tail(lines_list: &mut Vec<String>) {
while !lines_list.is_empty() && line_is_only_whitepace(lines_list.last().unwrap()) {
lines_list.pop();
}
}
fn end_the_last_structured_comments(
lines_list: &mut Vec<String>,
comment_tracker: &mut Vec<CommentDetail>,
leading_spaces: usize,
comment_starter: &str,
whitespace_char: char,
) {
let empty_line_count = count_ending_empty_lines(lines_list);
remove_empty_tail(lines_list);
while !comment_tracker.is_empty()
&& leading_spaces <= comment_tracker[comment_tracker.len() - 1].depth
{
let close_bracket_line = new_comment_closed_bracket(
comment_tracker[comment_tracker.len() - 1].depth,
comment_starter,
whitespace_char,
)
.unwrap();
lines_list.push(close_bracket_line);
comment_tracker.pop();
}
append_num_empty_lines(empty_line_count, lines_list);
}
fn pass_a_new_comment_that_we_dont_know_if_its_structured(
lines_list: &mut Vec<String>,
comment_tracker: &mut Vec<CommentDetail>,
leading_spaces: Option<usize>,
unsure_if_last_comment_was_structured: &mut bool,
line: &str,
) {
let comment = CommentDetail {
line: lines_list.len(),
depth: leading_spaces.unwrap(),
};
comment_tracker.push(comment);
*unsure_if_last_comment_was_structured = true;
lines_list.push(String::from(line));
}
fn count_and_remove_begining_whitespace(line: &str) -> Option<(usize, String)> {
let (leading_whitespace_option, line_no_leading_spaces) = chop_off_beginning_spaces(line);
leading_whitespace_option.map(|num_leading_whitespace| {
(num_leading_whitespace, line_no_leading_spaces.to_owned())
})
}
fn last_non_empty_line_before_index(
index: usize,
lines_list: &[String],
) -> Option<(usize, &str)> {
for i in (0..index).rev() {
if !line_is_only_whitepace(&lines_list[i]) {
return Some((i, &lines_list[i]));
}
}
None
}
fn add_open_bracket_to_last_comment(
lines_list: &mut Vec<String>,
comment_tracker: &mut [CommentDetail],
comment_starter: &str,
) {
let mut should_consume_closing_comment = false;
let line_of_latest_comment = comment_tracker[comment_tracker.len() - 1].line;
let last_solid_line_option =
last_non_empty_line_before_index(line_of_latest_comment, lines_list);
if let Some((_last_solid_line_index, line_before_open_bracket_comment)) =
last_solid_line_option
{
let (leading_spaces, line_no_leading_spaces) =
chop_off_beginning_spaces(line_before_open_bracket_comment);
let (is_a_comment, _, line_no_comment_opener) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
let latest_comment =
match count_and_remove_begining_whitespace(&lines_list[line_of_latest_comment]) {
Some(x) => x,
None => (0, String::from("")),
};
if is_a_comment
&& line_no_comment_opener.starts_with('<')
&& latest_comment.0 == leading_spaces.unwrap()
{
should_consume_closing_comment = true;
}
}
let line_with_no_bracket = lines_list[line_of_latest_comment].clone();
if should_consume_closing_comment {
let after_spaces = count_ending_empty_lines(lines_list);
remove_empty_tail(lines_list);
lines_list.pop();
let before_spaces = count_ending_empty_lines(lines_list);
remove_empty_tail(lines_list);
lines_list.pop();
append_num_empty_lines(before_spaces, lines_list);
lines_list.push(
make_comment_closed_and_open_bracket(&line_with_no_bracket, comment_starter)
.unwrap(),
);
append_num_empty_lines(after_spaces, lines_list);
} else {
lines_list[line_of_latest_comment] =
make_comment_open_bracket(&line_with_no_bracket, comment_starter).unwrap();
}
}
fn line_is_only_whitepace(str: &str) -> bool {
for char in str.chars() {
if char as u32 > 32 {
return false;
}
}
true
}
pub fn add_brackets(str: &str, filetype: &str) -> Result<String, ScfmtErr> {
let comment_starter = match EXTENSION_TO_COMMENT_STARTER_MAP.get(filetype) {
Some(x) => *x,
None => return Err(ScfmtErr::IncompatibleFileType),
};
let str = &remove_brackets(str, filetype)?;
let (whitespace_char, _tab_spaces) = determine_whitespace_type(str);
let mut comment_tracker: Vec<CommentDetail> = Vec::new();
let mut lines_list: Vec<String> = Vec::new();
let mut unsure_if_last_comment_was_structured = true;
let mut processed_line_count = 0;
for line in str.lines() {
processed_line_count += 1;
let (leading_spaces, line_no_leading_spaces) = chop_off_beginning_spaces(line);
let (is_a_comment, _, _) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
match leading_spaces {
Some(x) => {
if is_a_comment {
if !comment_tracker.is_empty() {
if unsure_if_last_comment_was_structured {
if x > comment_tracker[comment_tracker.len() - 1].depth {
add_open_bracket_to_last_comment(
&mut lines_list,
&mut comment_tracker,
comment_starter,
);
pass_a_new_comment_that_we_dont_know_if_its_structured(
&mut lines_list,
&mut comment_tracker,
leading_spaces,
&mut unsure_if_last_comment_was_structured,
line,
);
} else {
comment_tracker.pop();
end_the_last_structured_comments(
&mut lines_list,
&mut comment_tracker,
x,
comment_starter,
whitespace_char,
);
pass_a_new_comment_that_we_dont_know_if_its_structured(
&mut lines_list,
&mut comment_tracker,
leading_spaces,
&mut unsure_if_last_comment_was_structured,
line,
);
}
} else if x > comment_tracker[comment_tracker.len() - 1].depth {
pass_a_new_comment_that_we_dont_know_if_its_structured(
&mut lines_list,
&mut comment_tracker,
leading_spaces,
&mut unsure_if_last_comment_was_structured,
line,
);
} else {
end_the_last_structured_comments(
&mut lines_list,
&mut comment_tracker,
x,
comment_starter,
whitespace_char,
);
pass_a_new_comment_that_we_dont_know_if_its_structured(
&mut lines_list,
&mut comment_tracker,
leading_spaces,
&mut unsure_if_last_comment_was_structured,
line,
);
}
} else {
pass_a_new_comment_that_we_dont_know_if_its_structured(
&mut lines_list,
&mut comment_tracker,
leading_spaces,
&mut unsure_if_last_comment_was_structured,
line,
);
}
} else if !comment_tracker.is_empty() {
if unsure_if_last_comment_was_structured {
if x > comment_tracker[comment_tracker.len() - 1].depth {
add_open_bracket_to_last_comment(
&mut lines_list,
&mut comment_tracker,
comment_starter,
);
} else {
comment_tracker.pop();
end_the_last_structured_comments(
&mut lines_list,
&mut comment_tracker,
x,
comment_starter,
whitespace_char,
);
}
unsure_if_last_comment_was_structured = false;
lines_list.push(String::from(line));
} else if x > comment_tracker[comment_tracker.len() - 1].depth {
lines_list.push(String::from(line));
} else {
end_the_last_structured_comments(
&mut lines_list,
&mut comment_tracker,
x,
comment_starter,
whitespace_char,
);
lines_list.push(String::from(line));
}
} else {
lines_list.push(String::from(line));
}
}
None => {
lines_list.push("".to_owned());
}
}
}
if unsure_if_last_comment_was_structured && !comment_tracker.is_empty() {
comment_tracker.pop();
}
end_the_last_structured_comments(
&mut lines_list,
&mut comment_tracker,
0,
comment_starter,
whitespace_char,
);
let mut final_string = String::new();
for line in lines_list {
final_string.push_str(&line);
final_string.push('\n');
}
final_string.pop();
let line_diff = count_lines(str) - processed_line_count;
if line_diff > 0 {
for _ in 0..line_diff {
final_string.push('\n');
}
}
Ok(final_string)
}
pub fn null_existing_brackets(str: &str, filetype: &str) -> Result<String, ScfmtErr> {
let comment_starter = match EXTENSION_TO_COMMENT_STARTER_MAP.get(filetype) {
Some(x) => *x,
None => return Err(ScfmtErr::IncompatibleFileType),
};
let (whitespace_char, _tab_spaces) = determine_whitespace_type(str);
let mut lines_list = Vec::new();
let mut processed_line_count = 0;
for line in str.lines() {
processed_line_count += 1;
let (_leading_spaces, line_no_leading_spaces) = chop_off_beginning_spaces(line);
let (is_a_comment, space_after_comment_starter, line_no_comment_starter) =
remove_comment_notation_if_it_exists(line_no_leading_spaces, comment_starter);
if is_a_comment && line_no_comment_starter.starts_with('<')
|| line_no_comment_starter.starts_with('>')
{
let depth_option = count_and_remove_begining_whitespace(line);
let depth = match depth_option {
Some(x) => x.0,
None => 0,
};
let potential_space = if space_after_comment_starter { " " } else { "" };
lines_list.push(add_whitespace(
&(comment_starter.to_owned()
+ potential_space
+ "_"
+ &line_no_comment_starter),
depth,
whitespace_char,
));
} else if line_is_only_whitepace(line) {
lines_list.push("".to_owned());
} else {
lines_list.push(line.to_owned());
}
}
let mut final_string = String::new();
for line in lines_list {
final_string.push_str(&line);
final_string.push('\n');
}
final_string.pop();
let line_diff = count_lines(str) - processed_line_count;
if line_diff > 0 {
for _ in 0..line_diff {
final_string.push('\n');
}
}
Ok(final_string)
}
fn count_lines(str: &str) -> usize {
if str.is_empty() {
return 0;
}
let mut count = 1;
for c in str.chars() {
if c == '\n' {
count += 1;
}
}
count
}
pub fn remove_brackets_file(file: PathBuf) -> Result<(), ScfmtErr> {
let extenstion = match file.extension() {
Some(x) => match x.to_str() {
Some(x) => x,
None => return Err(ScfmtErr::CantConvertOsString),
},
None => return Err(ScfmtErr::IncompatibleFileType),
};
let contents = match fs::read_to_string(&file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantReadFileAsString),
};
let converted = remove_brackets(&contents, extenstion)?;
if converted != contents {
let mut output = match File::create(file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantCreatFile),
};
match write!(output, "{}", converted) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantWriteToFile),
};
}
Ok(())
}
pub fn null_existing_brackets_file(file: PathBuf) -> Result<(), ScfmtErr> {
let extenstion = match file.extension() {
Some(x) => match x.to_str() {
Some(x) => x,
None => return Err(ScfmtErr::CantConvertOsString),
},
None => return Err(ScfmtErr::IncompatibleFileType),
};
let contents = match fs::read_to_string(&file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantReadFileAsString),
};
let converted = null_existing_brackets(&contents, extenstion)?;
if converted != contents {
let mut output = match File::create(file) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantCreatFile),
};
match write!(output, "{}", converted) {
Ok(x) => x,
Err(_) => return Err(ScfmtErr::CantWriteToFile),
};
}
Ok(())
}
fn line_is_a_comment(str: &str, comment_starter: &str) -> bool {
match count_and_remove_begining_whitespace(str) {
Some(x) => {
let comment_starter_with_space = comment_starter.to_owned() + " ";
let str = x.1;
if str.starts_with(&comment_starter_with_space) {
true
} else {
str.starts_with(comment_starter)
}
}
None => false,
}
}
fn remove_comment_starter(str: &str, comment_starter: &str) -> String {
match count_and_remove_begining_whitespace(str) {
Some(x) => {
let str = x.1;
let mut line_no_comment_starter = "";
let comment_starter_with_space = comment_starter.to_owned() + " ";
if str.starts_with(&comment_starter_with_space) {
line_no_comment_starter = &str[comment_starter.len() + 1..];
} else if let Some(stripped) = str.strip_prefix(comment_starter) {
line_no_comment_starter = stripped;
}
line_no_comment_starter.to_string()
}
None => str.to_owned(),
}
}
fn count_ending_empty_lines(lines_list: &[String]) -> usize {
let mut count = 0;
for i in (0..lines_list.len()).rev() {
if !line_is_only_whitepace(&lines_list[i]) {
break;
}
count += 1;
}
count
}
fn append_num_empty_lines(num: usize, lines_list: &mut Vec<String>) {
for _ in 0..num {
lines_list.push("".to_owned());
}
}
pub fn remove_brackets(str: &str, filetype: &str) -> Result<String, ScfmtErr> {
let comment_starter = match EXTENSION_TO_COMMENT_STARTER_MAP.get(filetype) {
Some(x) => *x,
None => return Err(ScfmtErr::IncompatibleFileType),
};
let mut lines_list: Vec<String> = Vec::new();
let str = &format_str(str, filetype)?;
let (whitespace_char, _tab_spaces) = determine_whitespace_type(str);
let mut formatted_str = String::new();
let mut processed_line_count = 0;
for line in str.lines() {
processed_line_count += 1;
let line_no_leading_whitespace;
let leading_whitespace;
if let Some(x) = count_and_remove_begining_whitespace(line) {
leading_whitespace = x.0;
line_no_leading_whitespace = &x.1;
if line_is_a_comment(line_no_leading_whitespace, comment_starter) {
let line_no_comment_starter =
remove_comment_starter(line_no_leading_whitespace, comment_starter);
if let Some(line_no_brackets) = line_no_comment_starter.strip_prefix("<>") {
lines_list.push(
add_whitespace(
&(comment_starter.to_owned() + line_no_brackets),
leading_whitespace,
whitespace_char,
) + "\n",
);
} else if let Some(line_no_brackets) = line_no_comment_starter.strip_prefix('>')
{
lines_list.push(
add_whitespace(
&(comment_starter.to_owned() + line_no_brackets),
leading_whitespace,
whitespace_char,
) + "\n",
);
} else if line_no_comment_starter.starts_with('<') {
continue;
} else {
lines_list.push(line.to_owned() + "\n");
}
} else {
lines_list.push(line.to_owned() + "\n");
}
} else {
lines_list.push("\n".to_owned());
}
}
for line in lines_list {
formatted_str.push_str(&line);
}
formatted_str.pop();
let line_diff = count_lines(str) - processed_line_count;
if line_diff > 0 {
for _ in 0..line_diff {
formatted_str.push('\n');
}
}
Ok(formatted_str)
}
}