vnetod/src/domain/switch.rs

277 lines
8.6 KiB
Rust

//! Copyright (C) 2022, Dmitriy Pleshevskiy <dmitriy@pleshevski.ru>
//!
//! 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 <https://www.gnu.org/licenses/>.
//!
use super::{Section, SectionInfo};
pub type OnLineFn = Box<dyn Fn(&String)>;
pub struct Request<'args> {
pub content: &'args str,
pub sections: &'args [String],
pub on_line: Option<OnLineFn>,
}
pub fn execute(req: Request) -> String {
let choose_sections = req
.sections
.iter()
.map(|s| Section::parse(s.as_str()))
.collect::<Vec<_>>();
let mut current_section: Option<SectionInfo> = 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::<Vec<_>>();
SectionInfo {
enable_variable: should_enable_variable(&choose_sections, &current_sections),
disable_variable: should_disable_variable(&choose_sections, &current_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::<Vec<_>>();
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: &sections.into_iter().map(String::from).collect::<Vec<_>>(),
on_line: None,
});
assert_eq!(
output_data.lines().collect::<Vec<_>>(),
expected_output.lines().collect::<Vec<_>>()
);
}
#[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);
}
}
}
}