report which lines changed on up/down
set_local_db, toggle_after_line and set_dot_env now return a FileChange (path + before/after line pairs) instead of (), and main prints it after each toggle so 'up'/'down' shows exactly what the program rewrote in each env file.
This commit is contained in:
+287
-19
@@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
fmt,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
};
|
};
|
||||||
@@ -9,8 +10,27 @@ use crate::env::config::DevToolsConf;
|
|||||||
pub const GLOBAL_CONNECTION_PATH: &str = "conf/global/connections.env";
|
pub const GLOBAL_CONNECTION_PATH: &str = "conf/global/connections.env";
|
||||||
const LOCAL_CONNECTION_PATH: &str = "conf/local/.env";
|
const LOCAL_CONNECTION_PATH: &str = "conf/local/.env";
|
||||||
|
|
||||||
|
/// The lines a toggle operation rewrote in a given file, as (before, after) pairs.
|
||||||
|
pub struct FileChange {
|
||||||
|
pub path: String,
|
||||||
|
pub diffs: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FileChange {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.diffs.is_empty() {
|
||||||
|
return writeln!(f, "[!] {}: no changes", self.path);
|
||||||
|
}
|
||||||
|
writeln!(f, "[!] {} changed:", self.path)?;
|
||||||
|
for (before, after) in &self.diffs {
|
||||||
|
writeln!(f, " {} -> {}", before, after)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// writes global connections
|
/// writes global connections
|
||||||
pub fn set_local_db(start: bool, config: &DevToolsConf) -> Result<()> {
|
pub fn set_local_db(start: bool, config: &DevToolsConf) -> Result<FileChange> {
|
||||||
let global_connections_env = format!(
|
let global_connections_env = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
config.project_dir_as_string(),
|
config.project_dir_as_string(),
|
||||||
@@ -21,27 +41,39 @@ pub fn set_local_db(start: bool, config: &DevToolsConf) -> Result<()> {
|
|||||||
let reader: BufReader<File> = BufReader::new(file);
|
let reader: BufReader<File> = BufReader::new(file);
|
||||||
|
|
||||||
let mut modified_lines: Vec<String> = Vec::new();
|
let mut modified_lines: Vec<String> = Vec::new();
|
||||||
|
let mut diffs: Vec<(String, String)> = Vec::new();
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line_content = line?;
|
let line_content = line?;
|
||||||
let modified = if start && connections_env_comment(&line_content) {
|
let modified: Option<String> = if start && connections_env_comment(&line_content) {
|
||||||
format!("# {}", line_content)
|
Some(format!("# {}", line_content))
|
||||||
} else if !start && should_commented_out(&line_content) {
|
} else if !start && should_commented_out(&line_content) {
|
||||||
line_content[2..].to_string()
|
Some(line_content[2..].to_string())
|
||||||
} else {
|
} else {
|
||||||
line_content
|
None
|
||||||
};
|
};
|
||||||
modified_lines.push(modified);
|
|
||||||
|
match modified {
|
||||||
|
Some(new_line) => {
|
||||||
|
diffs.push((line_content, new_line.clone()));
|
||||||
|
modified_lines.push(new_line);
|
||||||
|
}
|
||||||
|
None => modified_lines.push(line_content),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(global_connections_env, modified_lines.join("\n"))
|
fs::write(&global_connections_env, modified_lines.join("\n"))
|
||||||
.context("Cannot write global connections env")?;
|
.context("Cannot write global connections env")?;
|
||||||
Ok(())
|
Ok(FileChange {
|
||||||
|
path: global_connections_env,
|
||||||
|
diffs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dot_env(start: bool, config: &DevToolsConf) -> Result<()> {
|
pub fn set_dot_env(start: bool, config: &DevToolsConf) -> Result<FileChange> {
|
||||||
toggle_after_line("#docker", 4, start, config)?;
|
let mut docker_change = toggle_after_line("#docker", 4, start, config)?;
|
||||||
toggle_after_line("#cidb", 2, !start, config)?;
|
let cidb_change = toggle_after_line("#cidb", 2, !start, config)?;
|
||||||
Ok(())
|
docker_change.diffs.extend(cidb_change.diffs);
|
||||||
|
Ok(docker_change)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CI_DB_MASTER_IP: &str = "ci.db.master.ip";
|
const CI_DB_MASTER_IP: &str = "ci.db.master.ip";
|
||||||
@@ -85,7 +117,7 @@ fn toggle_after_line(
|
|||||||
num_lines: usize,
|
num_lines: usize,
|
||||||
activate: bool,
|
activate: bool,
|
||||||
config: &DevToolsConf,
|
config: &DevToolsConf,
|
||||||
) -> Result<()> {
|
) -> Result<FileChange> {
|
||||||
let local_connection_env = format!(
|
let local_connection_env = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
config.project_dir_as_string(),
|
config.project_dir_as_string(),
|
||||||
@@ -96,6 +128,7 @@ fn toggle_after_line(
|
|||||||
let reader: BufReader<File> = BufReader::new(file);
|
let reader: BufReader<File> = BufReader::new(file);
|
||||||
|
|
||||||
let mut modified_lines: Vec<String> = Vec::new();
|
let mut modified_lines: Vec<String> = Vec::new();
|
||||||
|
let mut diffs: Vec<(String, String)> = Vec::new();
|
||||||
let mut relevant = false;
|
let mut relevant = false;
|
||||||
let mut counter: usize = 0;
|
let mut counter: usize = 0;
|
||||||
|
|
||||||
@@ -111,21 +144,34 @@ fn toggle_after_line(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if relevant {
|
let modified: Option<String> = if relevant {
|
||||||
modified_lines.push(toggle(&line_content, activate));
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
let new_line = toggle(&line_content, activate);
|
||||||
|
(new_line != line_content).then_some(new_line)
|
||||||
} else if let Some(key) = connection_key(&line_content) {
|
} else if let Some(key) = connection_key(&line_content) {
|
||||||
let value = if activate { "staging" } else { "ci" };
|
let value = if activate { "staging" } else { "ci" };
|
||||||
modified_lines.push(format!("{}='{}'", key, value));
|
let new_line = format!("{}='{}'", key, value);
|
||||||
|
(new_line != line_content).then_some(new_line)
|
||||||
} else {
|
} else {
|
||||||
modified_lines.push(line_content);
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match modified {
|
||||||
|
Some(new_line) => {
|
||||||
|
diffs.push((line_content, new_line.clone()));
|
||||||
|
modified_lines.push(new_line);
|
||||||
|
}
|
||||||
|
None => modified_lines.push(line_content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(local_connection_env, modified_lines.join("\n"))
|
fs::write(&local_connection_env, modified_lines.join("\n"))
|
||||||
.context("Cannot write local connection env")?;
|
.context("Cannot write local connection env")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(FileChange {
|
||||||
|
path: local_connection_env,
|
||||||
|
diffs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `connection` key as it appears on the given line (either the
|
/// Returns the `connection` key as it appears on the given line (either the
|
||||||
@@ -156,6 +202,228 @@ pub(crate) fn toggle(line: &str, activate: bool) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// An isolated `conf/global`/`conf/local` tree under the OS temp dir, for
|
||||||
|
/// exercising `set_local_db`/`set_dot_env` against real files without
|
||||||
|
/// touching this repo's own `conf/` directory. Namespaced by process id
|
||||||
|
/// so concurrent `cargo test` runs can't collide on the same path, and
|
||||||
|
/// removed on drop so no stale directories accumulate.
|
||||||
|
struct TempProjectDir(PathBuf);
|
||||||
|
|
||||||
|
impl TempProjectDir {
|
||||||
|
fn new(name: &str) -> Self {
|
||||||
|
let dir = std::env::temp_dir().join(format!(
|
||||||
|
"dev_tools_parse_test_{}_{}",
|
||||||
|
std::process::id(),
|
||||||
|
name
|
||||||
|
));
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
fs::create_dir_all(dir.join("conf/global")).unwrap();
|
||||||
|
fs::create_dir_all(dir.join("conf/local")).unwrap();
|
||||||
|
Self(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TempProjectDir {
|
||||||
|
type Target = Path;
|
||||||
|
fn deref(&self) -> &Path {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TempProjectDir {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = fs::remove_dir_all(&self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_for(dir: &Path) -> DevToolsConf {
|
||||||
|
DevToolsConf {
|
||||||
|
container_service: String::new(),
|
||||||
|
container_dir: String::new(),
|
||||||
|
project_directory: PathBuf::from(format!("{}/", dir.display())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_local_db_records_diffs_when_commenting_out() {
|
||||||
|
let dir = TempProjectDir::new("comment_out");
|
||||||
|
fs::write(
|
||||||
|
dir.join(GLOBAL_CONNECTION_PATH),
|
||||||
|
"ci.db.master.ip=127.0.0.1\nci.db.master.port=5432\nci.db.master.user=admin\nRCC_CI_DB_MASTER_IP=127.0.0.1",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let change = set_local_db(true, &config_for(&dir)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
change.diffs,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
"ci.db.master.ip=127.0.0.1".to_string(),
|
||||||
|
"# ci.db.master.ip=127.0.0.1".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ci.db.master.port=5432".to_string(),
|
||||||
|
"# ci.db.master.port=5432".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"RCC_CI_DB_MASTER_IP=127.0.0.1".to_string(),
|
||||||
|
"# RCC_CI_DB_MASTER_IP=127.0.0.1".to_string()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_local_db_records_diffs_when_uncommenting() {
|
||||||
|
let dir = TempProjectDir::new("uncomment");
|
||||||
|
fs::write(
|
||||||
|
dir.join(GLOBAL_CONNECTION_PATH),
|
||||||
|
"# ci.db.master.ip=127.0.0.1\n# ci.db.master.port=5432\nci.db.master.user=admin",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let change = set_local_db(false, &config_for(&dir)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
change.diffs,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
"# ci.db.master.ip=127.0.0.1".to_string(),
|
||||||
|
"ci.db.master.ip=127.0.0.1".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"# ci.db.master.port=5432".to_string(),
|
||||||
|
"ci.db.master.port=5432".to_string()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toggle_after_line_records_diffs_for_toggled_section() {
|
||||||
|
let dir = TempProjectDir::new("toggle_section");
|
||||||
|
fs::write(
|
||||||
|
dir.join(LOCAL_CONNECTION_PATH),
|
||||||
|
"#docker\nDOCKER_HOST=tcp://localhost:2375\n#DOCKER_TLS_VERIFY=1\n#DOCKER_CERT_PATH=/certs\nDOCKER_API_VERSION=1.41\nother=1",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let change = toggle_after_line("#docker", 4, true, &config_for(&dir)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
change.diffs,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
"#DOCKER_TLS_VERIFY=1".to_string(),
|
||||||
|
"DOCKER_TLS_VERIFY=1".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"#DOCKER_CERT_PATH=/certs".to_string(),
|
||||||
|
"DOCKER_CERT_PATH=/certs".to_string()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toggle_after_line_records_diffs_for_both_connection_keys() {
|
||||||
|
let dir = TempProjectDir::new("connection_keys");
|
||||||
|
fs::write(
|
||||||
|
dir.join(LOCAL_CONNECTION_PATH),
|
||||||
|
"#docker\nline1\nline2\nline3\nline4\nconnection='staging'\nRCC_CONNECTION='staging'",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let change = toggle_after_line("#cidb", 2, false, &config_for(&dir)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
change.diffs,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
"connection='staging'".to_string(),
|
||||||
|
"connection='ci'".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"RCC_CONNECTION='staging'".to_string(),
|
||||||
|
"RCC_CONNECTION='ci'".to_string()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_dot_env_merges_diffs_from_both_sections() {
|
||||||
|
let dir = TempProjectDir::new("merge_sections");
|
||||||
|
fs::write(
|
||||||
|
dir.join(LOCAL_CONNECTION_PATH),
|
||||||
|
"#docker\nDOCKER_HOST=tcp://localhost:2375\n#DOCKER_TLS_VERIFY=1\n#DOCKER_CERT_PATH=/certs\nDOCKER_API_VERSION=1.41\n#cidb\nci.db.master.ip=127.0.0.1\nci.db.master.port=5432\nconnection='staging'\nRCC_CONNECTION='staging'",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let change = set_dot_env(true, &config_for(&dir)).unwrap();
|
||||||
|
|
||||||
|
assert!(change.path.ends_with(LOCAL_CONNECTION_PATH));
|
||||||
|
assert_eq!(change.diffs.len(), 6);
|
||||||
|
for expected in [
|
||||||
|
("#DOCKER_TLS_VERIFY=1", "DOCKER_TLS_VERIFY=1"),
|
||||||
|
("#DOCKER_CERT_PATH=/certs", "DOCKER_CERT_PATH=/certs"),
|
||||||
|
("ci.db.master.ip=127.0.0.1", "#ci.db.master.ip=127.0.0.1"),
|
||||||
|
("ci.db.master.port=5432", "#ci.db.master.port=5432"),
|
||||||
|
("connection='staging'", "connection='ci'"),
|
||||||
|
("RCC_CONNECTION='staging'", "RCC_CONNECTION='ci'"),
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
change
|
||||||
|
.diffs
|
||||||
|
.contains(&(expected.0.to_string(), expected.1.to_string())),
|
||||||
|
"missing diff {:?}",
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_change_display_no_diffs() {
|
||||||
|
let change = FileChange {
|
||||||
|
path: "some/path".to_string(),
|
||||||
|
diffs: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(format!("{}", change), "[!] some/path: no changes\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_change_display_with_diffs() {
|
||||||
|
let change = FileChange {
|
||||||
|
path: "some/path".to_string(),
|
||||||
|
diffs: vec![(
|
||||||
|
"connection='ci'".to_string(),
|
||||||
|
"connection='staging'".to_string(),
|
||||||
|
)],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", change),
|
||||||
|
"[!] some/path changed:\n connection='ci' -> connection='staging'\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_change_display_with_multiple_diffs() {
|
||||||
|
let change = FileChange {
|
||||||
|
path: "some/path".to_string(),
|
||||||
|
diffs: vec![
|
||||||
|
("a=1".to_string(), "#a=1".to_string()),
|
||||||
|
("b=2".to_string(), "#b=2".to_string()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", change),
|
||||||
|
"[!] some/path changed:\n a=1 -> #a=1\n b=2 -> #b=2\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn toggle_activate_removes_leading_hash() {
|
fn toggle_activate_removes_leading_hash() {
|
||||||
|
|||||||
+2
-2
@@ -24,13 +24,13 @@ fn main() -> Result<()> {
|
|||||||
start_docker_compose(&config.container_service, &config.container_dir, start)?;
|
start_docker_compose(&config.container_service, &config.container_dir, start)?;
|
||||||
if args.legacy {
|
if args.legacy {
|
||||||
println!("[!] Legacy mode active.");
|
println!("[!] Legacy mode active.");
|
||||||
set_local_db(start, &config)?;
|
print!("{}", set_local_db(start, &config)?);
|
||||||
toggle_index(start, &config)?;
|
toggle_index(start, &config)?;
|
||||||
} else {
|
} else {
|
||||||
println!("[!] Legacy mode deactivated.");
|
println!("[!] Legacy mode deactivated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
set_dot_env(start, &config)?;
|
print!("{}", set_dot_env(start, &config)?);
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"[!] Don't forget your interpreter! Project directory: {}",
|
"[!] Don't forget your interpreter! Project directory: {}",
|
||||||
|
|||||||
Reference in New Issue
Block a user