pop_common/
helpers.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use crate::Error;
4use std::{
5	collections::HashMap,
6	fs,
7	io::{Read, Write},
8	path::{Component, Path, PathBuf},
9};
10
11/// Replaces occurrences of specified strings in a file with new values.
12///
13/// # Arguments
14///
15/// * `file_path` - A `PathBuf` specifying the path to the file to be modified.
16/// * `replacements` - A `HashMap` where each key-value pair represents a target string and its
17///   corresponding replacement string.
18pub fn replace_in_file(file_path: PathBuf, replacements: HashMap<&str, &str>) -> Result<(), Error> {
19	// Read the file content
20	let mut file_content = String::new();
21	fs::File::open(&file_path)?.read_to_string(&mut file_content)?;
22	// Perform the replacements
23	let mut modified_content = file_content;
24	for (target, replacement) in &replacements {
25		modified_content = modified_content.replace(target, replacement);
26	}
27	// Write the modified content back to the file
28	let mut file = fs::File::create(&file_path)?;
29	file.write_all(modified_content.as_bytes())?;
30	Ok(())
31}
32
33/// Gets the last component (name of a project) of a path or returns a default value if the path has
34/// no valid last component.
35///
36/// # Arguments
37/// * `path` - Location path of the project.
38/// * `default` - The default string to return if the path has no valid last component.
39pub fn get_project_name_from_path<'a>(path: &'a Path, default: &'a str) -> &'a str {
40	path.file_name().and_then(|name| name.to_str()).unwrap_or(default)
41}
42
43/// Transforms a path without prefix into a relative path starting at the current directory.
44///
45/// # Arguments
46/// * `path` - The path to be prefixed if needed.
47pub fn prefix_with_current_dir_if_needed(path: PathBuf) -> PathBuf {
48	let components = &path.components().collect::<Vec<Component>>();
49	if !components.is_empty() {
50		// If the first component is a normal component, we prefix the path with the current dir
51		if let Component::Normal(_) = components[0] {
52			return <Component<'_> as AsRef<Path>>::as_ref(&Component::CurDir).join(path);
53		}
54	}
55	path
56}
57
58/// Returns the relative path from `base` to `full` if `full` is inside `base`.
59/// If `full` is outside `base`, returns the absolute path instead.
60///
61/// # Arguments
62/// * `base` - The base directory to compare against.
63/// * `full` - The full path to be shortened.
64pub fn get_relative_or_absolute_path(base: &Path, full: &Path) -> PathBuf {
65	match full.strip_prefix(base) {
66		Ok(relative) => relative.to_path_buf(),
67		// If prefix is different, return the full path
68		Err(_) => full.to_path_buf(),
69	}
70}
71
72#[cfg(test)]
73mod tests {
74	use super::*;
75	use anyhow::Result;
76	use std::fs;
77
78	#[test]
79	fn test_replace_in_file() -> Result<(), Error> {
80		let temp_dir = tempfile::tempdir()?;
81		let file_path = temp_dir.path().join("file.toml");
82		let mut file = fs::File::create(temp_dir.path().join("file.toml"))?;
83		writeln!(file, "name = test, version = 5.0.0")?;
84		let mut replacements_in_cargo = HashMap::new();
85		replacements_in_cargo.insert("test", "changed_name");
86		replacements_in_cargo.insert("5.0.0", "5.0.1");
87		replace_in_file(file_path.clone(), replacements_in_cargo)?;
88		let content = fs::read_to_string(file_path).expect("Could not read file");
89		assert_eq!(content.trim(), "name = changed_name, version = 5.0.1");
90		Ok(())
91	}
92
93	#[test]
94	fn get_project_name_from_path_works() -> Result<(), Error> {
95		let path = Path::new("./path/to/project/my-parachain");
96		assert_eq!(get_project_name_from_path(path, "default_name"), "my-parachain");
97		Ok(())
98	}
99
100	#[test]
101	fn get_project_name_from_path_default_value() -> Result<(), Error> {
102		let path = Path::new("./");
103		assert_eq!(get_project_name_from_path(path, "my-contract"), "my-contract");
104		Ok(())
105	}
106
107	#[test]
108	fn prefix_with_current_dir_if_needed_works_well() {
109		let no_prefixed_path = PathBuf::from("my/path".to_string());
110		let current_dir_prefixed_path = PathBuf::from("./my/path".to_string());
111		let parent_dir_prefixed_path = PathBuf::from("../my/path".to_string());
112		let root_dir_prefixed_path = PathBuf::from("/my/path".to_string());
113		let empty_path = PathBuf::from("".to_string());
114
115		assert_eq!(
116			prefix_with_current_dir_if_needed(no_prefixed_path),
117			PathBuf::from("./my/path/".to_string())
118		);
119		assert_eq!(
120			prefix_with_current_dir_if_needed(current_dir_prefixed_path),
121			PathBuf::from("./my/path/".to_string())
122		);
123		assert_eq!(
124			prefix_with_current_dir_if_needed(parent_dir_prefixed_path),
125			PathBuf::from("../my/path/".to_string())
126		);
127		assert_eq!(
128			prefix_with_current_dir_if_needed(root_dir_prefixed_path),
129			PathBuf::from("/my/path/".to_string())
130		);
131		assert_eq!(prefix_with_current_dir_if_needed(empty_path), PathBuf::from("".to_string()));
132	}
133
134	#[test]
135	fn get_relative_or_absolute_path_works() {
136		[
137			("/path/to/project", "/path/to/project", ""),
138			("/path/to/project", "/path/to/src", "/path/to/src"),
139			("/path/to/project", "/path/to/project/main.rs", "main.rs"),
140			("/path/to/project", "/path/to/project/../main.rs", "../main.rs"),
141			("/path/to/project", "/path/to/project/src/main.rs", "src/main.rs"),
142		]
143		.into_iter()
144		.for_each(|(base, full, expected)| {
145			assert_eq!(
146				get_relative_or_absolute_path(Path::new(base), Path::new(full)),
147				Path::new(expected)
148			);
149		});
150	}
151}