277 lines
8.6 KiB
Rust
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, ¤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::<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: §ions.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);
|
|
}
|
|
}
|
|
}
|
|
}
|