//! Copyright (C) 2022, Dmitriy Pleshevskiy //! //! vnetod is free software: you can redistribute it and/or modify //! it under the terms of the GNU General Public License as published by //! the Free Software Foundation, either version 3 of the License, or //! (at your option) any later version. //! //! vnetod is distributed in the hope that it will be useful, //! but WITHOUT ANY WARRANTY; without even the implied warranty of //! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //! GNU General Public License for more details. //! //! You should have received a copy of the GNU General Public License //! along with vnetod. If not, see . //! use super::{Section, SectionInfo}; pub type OnLineFn = Box; pub struct Request<'args> { pub content: &'args str, pub sections: &'args [String], pub on_line: Option, } pub fn execute(req: Request) -> String { let choose_sections = req .sections .iter() .map(|s| Section::parse(s.as_str())) .collect::>(); let mut current_section: Option = None; let mut new_content = String::new(); for line in req.content.split_inclusive('\n') { let new_line = if is_section_end(line) { current_section = None; line.to_string() } else if let Some(section_info) = line.strip_prefix("### ") { current_section = section_info.split_whitespace().next().map(|r| { let current_sections = r.split(',').map(Section::parse).collect::>(); SectionInfo { enable_variable: should_enable_variable(&choose_sections, ¤t_sections), disable_variable: should_disable_variable(&choose_sections, ¤t_sections), } }); line.to_string() } else if let Some(section_info) = current_section.as_ref() { let trimmed_line = line.trim_start_matches(['#', ' ']); let is_var = is_variable(trimmed_line); if is_var && section_info.enable_variable { String::from(trimmed_line) } else if is_var && section_info.disable_variable { format!("# {}", trimmed_line) } else { line.to_string() } } else { line.to_string() }; new_content.push_str(&new_line); if let Some(on_line) = req.on_line.as_ref() { on_line(&new_line); } } new_content } fn is_variable(trimmed_line: &str) -> bool { trimmed_line .chars() .filter(|ch| (*ch != '_' && ch.is_ascii_punctuation()) || ch.is_whitespace()) .enumerate() .find(|(_, ch)| *ch == '=') .map_or(false, |(i, _)| i == 0) } fn is_section_end(line: &str) -> bool { line.trim().is_empty() } fn should_enable_variable(choose_sections: &[Section], current_sections: &[Section]) -> bool { let cross_sections = choose_sections .iter() .filter(|s| { s.namespace.is_some() && current_sections .iter() .any(|s2| s.namespace == s2.namespace) }) .collect::>(); if cross_sections.is_empty() { choose_sections.iter().any(|s| { if s.namespace.is_none() { current_sections.iter().any(|s2| s.name == s2.name) } else { current_sections.contains(s) } }) } else { cross_sections.iter().any(|s| current_sections.contains(s)) } } fn should_disable_variable(choose_sections: &[Section], current_sections: &[Section]) -> bool { choose_sections.is_empty() || choose_sections.iter().any(|s| s.namespace.is_none()) || choose_sections .iter() .filter(|s| s.namespace.is_some()) .any(|s| { current_sections .iter() .any(|s2| s.namespace == s2.namespace && s.name != s2.name) }) } #[cfg(test)] mod tests { use super::*; const BASE_ENV: &str = include_str!("../../test_data/base_env"); fn make_test(input: &str, expected_output: &str, sections: Vec<&str>) { let output_data = execute(Request { content: input, sections: §ions.into_iter().map(String::from).collect::>(), on_line: None, }); assert_eq!( output_data.lines().collect::>(), expected_output.lines().collect::>() ); } #[test] fn should_disable_all_sections() { make_test(include_str!("../../test_data/all_env"), BASE_ENV, vec![]); } #[test] fn should_enable_local_sections() { make_test( BASE_ENV, include_str!("../../test_data/should_enable_local"), vec!["local"], ); } #[test] fn should_enable_staging_sections() { make_test( BASE_ENV, include_str!("../../test_data/should_enable_staging"), vec!["staging"], ); } #[test] fn should_use_debug_in_staging_section() { make_test( BASE_ENV, include_str!("../../test_data/should_use_debug_in_staging"), vec!["staging", "debug:on"], ); } #[test] fn should_use_staging_db_in_local_section() { make_test( BASE_ENV, include_str!("../../test_data/should_use_staging_db_in_local"), vec!["local", "db:staging"], ); } mod utils { use super::*; #[test] fn should_not_enable_variables() { assert!(!should_enable_variable( &[Section::new("local")], &[Section::new("staging")] )); assert!(!should_enable_variable( &[Section::with_namespace("db", "local")], &[Section::new("local")] )); assert!(!should_enable_variable( &[Section::with_namespace("db", "local")], &[Section::with_namespace("db", "staging")] )); assert!(!should_enable_variable( &[ Section::new("staging"), Section::with_namespace("debug", "on") ], &[ Section::with_namespace("debug", "off"), Section::new("staging") ] )); } #[test] fn should_enable_variables() { assert!(should_enable_variable( &[Section::new("local")], &[Section::new("local")] )); assert!(should_enable_variable( &[Section::new("local")], &[Section::with_namespace("db", "local")] )); assert!(should_enable_variable( &[Section::with_namespace("db", "local")], &[Section::with_namespace("db", "local")] )); assert!(should_enable_variable( &[ Section::new("local"), Section::with_namespace("debug", "on") ], &[ Section::with_namespace("debug", "on"), Section::new("staging") ] )); } #[test] fn should_not_disable_variables() { assert!(!should_disable_variable( &[Section::with_namespace("debug", "on")], &[Section::new("local")] )); } #[test] fn should_disable_variables() { assert!(should_disable_variable( &[Section::new("local")], &[Section::with_namespace("debug", "off")] )); assert!(should_disable_variable( &[], &[Section::with_namespace("debug", "off")] )); assert!(should_disable_variable( &[Section::with_namespace("debug", "on")], &[Section::with_namespace("debug", "off")] )); } #[test] fn should_check_line_on_variable() { let test_cases = [ ("VAR=10", true), ("THIS_IS_MY_VAR='hello world'", true), ("hello world", false), ("staging/production", false), ("staging/production=value", false), ]; for (input, expected) in test_cases { assert_eq!(is_variable(input), expected); } } } }