Compare commits
2 Commits
developmen
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
8285fdb7b9 | ||
|
1ebd591f19 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -22,4 +22,8 @@ Cargo.lock
|
|||||||
/rhai_test_template
|
/rhai_test_template
|
||||||
/rhai_test_download
|
/rhai_test_download
|
||||||
/rhai_test_fs
|
/rhai_test_fs
|
||||||
run_rhai_tests.log
|
run_rhai_tests.log
|
||||||
|
new_location
|
||||||
|
log.txt
|
||||||
|
file.txt
|
||||||
|
fix_doc*
|
@ -1,9 +1,9 @@
|
|||||||
use std::process::Command;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::fs;
|
|
||||||
use std::fmt;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
// Define a custom error type for download operations
|
// Define a custom error type for download operations
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -26,11 +26,17 @@ pub enum DownloadError {
|
|||||||
impl fmt::Display for DownloadError {
|
impl fmt::Display for DownloadError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DownloadError::CreateDirectoryFailed(e) => write!(f, "Error creating directories: {}", e),
|
DownloadError::CreateDirectoryFailed(e) => {
|
||||||
|
write!(f, "Error creating directories: {}", e)
|
||||||
|
}
|
||||||
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
||||||
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
||||||
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
||||||
DownloadError::FileTooSmall(size, min) => write!(f, "Error: Downloaded file is too small ({}KB < {}KB)", size, min),
|
DownloadError::FileTooSmall(size, min) => write!(
|
||||||
|
f,
|
||||||
|
"Error: Downloaded file is too small ({}KB < {}KB)",
|
||||||
|
size, min
|
||||||
|
),
|
||||||
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
||||||
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
||||||
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
||||||
@ -74,12 +80,18 @@ impl Error for DownloadError {
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```no_run
|
||||||
* // Download a file with no minimum size requirement
|
* use sal::os::download;
|
||||||
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
|
||||||
*
|
*
|
||||||
* // Download a file with minimum size requirement of 100KB
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
* // Download a file with no minimum size requirement
|
||||||
|
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
||||||
|
*
|
||||||
|
* // Download a file with minimum size requirement of 100KB
|
||||||
|
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
||||||
|
*
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* # Notes
|
* # Notes
|
||||||
@ -91,30 +103,41 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
// Create parent directories if they don't exist
|
// Create parent directories if they don't exist
|
||||||
let dest_path = Path::new(dest);
|
let dest_path = Path::new(dest);
|
||||||
fs::create_dir_all(dest_path).map_err(DownloadError::CreateDirectoryFailed)?;
|
fs::create_dir_all(dest_path).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||||
|
|
||||||
// Extract filename from URL
|
// Extract filename from URL
|
||||||
let filename = match url.split('/').last() {
|
let filename = match url.split('/').last() {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => return Err(DownloadError::InvalidUrl("cannot extract filename".to_string()))
|
None => {
|
||||||
|
return Err(DownloadError::InvalidUrl(
|
||||||
|
"cannot extract filename".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a full path for the downloaded file
|
// Create a full path for the downloaded file
|
||||||
let file_path = format!("{}/{}", dest.trim_end_matches('/'), filename);
|
let file_path = format!("{}/{}", dest.trim_end_matches('/'), filename);
|
||||||
|
|
||||||
// Create a temporary path for downloading
|
// Create a temporary path for downloading
|
||||||
let temp_path = format!("{}.download", file_path);
|
let temp_path = format!("{}.download", file_path);
|
||||||
|
|
||||||
// Use curl to download the file with progress bar
|
// Use curl to download the file with progress bar
|
||||||
println!("Downloading {} to {}", url, file_path);
|
println!("Downloading {} to {}", url, file_path);
|
||||||
let output = Command::new("curl")
|
let output = Command::new("curl")
|
||||||
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
.args(&[
|
||||||
|
"--progress-bar",
|
||||||
|
"--location",
|
||||||
|
"--fail",
|
||||||
|
"--output",
|
||||||
|
&temp_path,
|
||||||
|
url,
|
||||||
|
])
|
||||||
.status()
|
.status()
|
||||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||||
|
|
||||||
if !output.success() {
|
if !output.success() {
|
||||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show file size after download
|
// Show file size after download
|
||||||
match fs::metadata(&temp_path) {
|
match fs::metadata(&temp_path) {
|
||||||
Ok(metadata) => {
|
Ok(metadata) => {
|
||||||
@ -122,14 +145,20 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
let size_kb = size_bytes / 1024;
|
let size_kb = size_bytes / 1024;
|
||||||
let size_mb = size_kb / 1024;
|
let size_mb = size_kb / 1024;
|
||||||
if size_mb > 1 {
|
if size_mb > 1 {
|
||||||
println!("Download complete! File size: {:.2} MB", size_bytes as f64 / (1024.0 * 1024.0));
|
println!(
|
||||||
|
"Download complete! File size: {:.2} MB",
|
||||||
|
size_bytes as f64 / (1024.0 * 1024.0)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("Download complete! File size: {:.2} KB", size_bytes as f64 / 1024.0);
|
println!(
|
||||||
|
"Download complete! File size: {:.2} KB",
|
||||||
|
size_bytes as f64 / 1024.0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => println!("Download complete!"),
|
Err(_) => println!("Download complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size if minimum size is specified
|
// Check file size if minimum size is specified
|
||||||
if min_size_kb > 0 {
|
if min_size_kb > 0 {
|
||||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||||
@ -139,57 +168,59 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a compressed file that needs extraction
|
// Check if it's a compressed file that needs extraction
|
||||||
let lower_url = url.to_lowercase();
|
let lower_url = url.to_lowercase();
|
||||||
let is_archive = lower_url.ends_with(".tar.gz") ||
|
let is_archive = lower_url.ends_with(".tar.gz")
|
||||||
lower_url.ends_with(".tgz") ||
|
|| lower_url.ends_with(".tgz")
|
||||||
lower_url.ends_with(".tar") ||
|
|| lower_url.ends_with(".tar")
|
||||||
lower_url.ends_with(".zip");
|
|| lower_url.ends_with(".zip");
|
||||||
|
|
||||||
if is_archive {
|
if is_archive {
|
||||||
// Extract the file using the appropriate command with progress indication
|
// Extract the file using the appropriate command with progress indication
|
||||||
println!("Extracting {} to {}", temp_path, dest);
|
println!("Extracting {} to {}", temp_path, dest);
|
||||||
let output = if lower_url.ends_with(".zip") {
|
let output = if lower_url.ends_with(".zip") {
|
||||||
Command::new("unzip")
|
Command::new("unzip")
|
||||||
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
|
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
|
||||||
.status()
|
.status()
|
||||||
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
|
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
|
||||||
Command::new("tar")
|
Command::new("tar")
|
||||||
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
|
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||||
.status()
|
.status()
|
||||||
} else {
|
} else {
|
||||||
Command::new("tar")
|
Command::new("tar")
|
||||||
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
|
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||||
.status()
|
.status()
|
||||||
};
|
};
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(DownloadError::ExtractionFailed("Error extracting archive".to_string()));
|
return Err(DownloadError::ExtractionFailed(
|
||||||
|
"Error extracting archive".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show number of extracted files
|
// Show number of extracted files
|
||||||
match fs::read_dir(dest) {
|
match fs::read_dir(dest) {
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
let count = entries.count();
|
let count = entries.count();
|
||||||
println!("Extraction complete! Extracted {} files/directories", count);
|
println!("Extraction complete! Extracted {} files/directories", count);
|
||||||
},
|
}
|
||||||
Err(_) => println!("Extraction complete!"),
|
Err(_) => println!("Extraction complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the temporary file
|
// Remove the temporary file
|
||||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||||
|
|
||||||
Ok(dest.to_string())
|
Ok(dest.to_string())
|
||||||
} else {
|
} else {
|
||||||
// Just rename the temporary file to the final destination
|
// Just rename the temporary file to the final destination
|
||||||
fs::rename(&temp_path, &file_path).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
fs::rename(&temp_path, &file_path).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||||
|
|
||||||
Ok(file_path)
|
Ok(file_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,12 +241,18 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```no_run
|
||||||
* // Download a file with no minimum size requirement
|
* use sal::os::download_file;
|
||||||
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
|
||||||
*
|
*
|
||||||
* // Download a file with minimum size requirement of 100KB
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
* // Download a file with no minimum size requirement
|
||||||
|
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
||||||
|
*
|
||||||
|
* // Download a file with minimum size requirement of 100KB
|
||||||
|
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
||||||
|
*
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||||
@ -224,21 +261,28 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
if let Some(parent) = dest_path.parent() {
|
if let Some(parent) = dest_path.parent() {
|
||||||
fs::create_dir_all(parent).map_err(DownloadError::CreateDirectoryFailed)?;
|
fs::create_dir_all(parent).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary path for downloading
|
// Create a temporary path for downloading
|
||||||
let temp_path = format!("{}.download", dest);
|
let temp_path = format!("{}.download", dest);
|
||||||
|
|
||||||
// Use curl to download the file with progress bar
|
// Use curl to download the file with progress bar
|
||||||
println!("Downloading {} to {}", url, dest);
|
println!("Downloading {} to {}", url, dest);
|
||||||
let output = Command::new("curl")
|
let output = Command::new("curl")
|
||||||
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
.args(&[
|
||||||
|
"--progress-bar",
|
||||||
|
"--location",
|
||||||
|
"--fail",
|
||||||
|
"--output",
|
||||||
|
&temp_path,
|
||||||
|
url,
|
||||||
|
])
|
||||||
.status()
|
.status()
|
||||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||||
|
|
||||||
if !output.success() {
|
if !output.success() {
|
||||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show file size after download
|
// Show file size after download
|
||||||
match fs::metadata(&temp_path) {
|
match fs::metadata(&temp_path) {
|
||||||
Ok(metadata) => {
|
Ok(metadata) => {
|
||||||
@ -246,14 +290,20 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
let size_kb = size_bytes / 1024;
|
let size_kb = size_bytes / 1024;
|
||||||
let size_mb = size_kb / 1024;
|
let size_mb = size_kb / 1024;
|
||||||
if size_mb > 1 {
|
if size_mb > 1 {
|
||||||
println!("Download complete! File size: {:.2} MB", size_bytes as f64 / (1024.0 * 1024.0));
|
println!(
|
||||||
|
"Download complete! File size: {:.2} MB",
|
||||||
|
size_bytes as f64 / (1024.0 * 1024.0)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("Download complete! File size: {:.2} KB", size_bytes as f64 / 1024.0);
|
println!(
|
||||||
|
"Download complete! File size: {:.2} KB",
|
||||||
|
size_bytes as f64 / 1024.0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => println!("Download complete!"),
|
Err(_) => println!("Download complete!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size if minimum size is specified
|
// Check file size if minimum size is specified
|
||||||
if min_size_kb > 0 {
|
if min_size_kb > 0 {
|
||||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||||
@ -263,10 +313,10 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename the temporary file to the final destination
|
// Rename the temporary file to the final destination
|
||||||
fs::rename(&temp_path, dest).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
fs::rename(&temp_path, dest).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||||
|
|
||||||
Ok(dest.to_string())
|
Ok(dest.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,27 +334,38 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
|||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```no_run
|
||||||
* // Make a file executable
|
* use sal::os::chmod_exec;
|
||||||
* chmod_exec("/path/to/file")?;
|
*
|
||||||
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
* // Make a file executable
|
||||||
|
* chmod_exec("/path/to/file")?;
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||||
let path_obj = Path::new(path);
|
let path_obj = Path::new(path);
|
||||||
|
|
||||||
// Check if the path exists and is a file
|
// Check if the path exists and is a file
|
||||||
if !path_obj.exists() {
|
if !path_obj.exists() {
|
||||||
return Err(DownloadError::NotAFile(format!("Path does not exist: {}", path)));
|
return Err(DownloadError::NotAFile(format!(
|
||||||
|
"Path does not exist: {}",
|
||||||
|
path
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path_obj.is_file() {
|
if !path_obj.is_file() {
|
||||||
return Err(DownloadError::NotAFile(format!("Path is not a file: {}", path)));
|
return Err(DownloadError::NotAFile(format!(
|
||||||
|
"Path is not a file: {}",
|
||||||
|
path
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current permissions
|
// Get current permissions
|
||||||
let metadata = fs::metadata(path).map_err(DownloadError::FileMetadataError)?;
|
let metadata = fs::metadata(path).map_err(DownloadError::FileMetadataError)?;
|
||||||
let mut permissions = metadata.permissions();
|
let mut permissions = metadata.permissions();
|
||||||
|
|
||||||
// Set executable bit for user, group, and others
|
// Set executable bit for user, group, and others
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
@ -314,47 +375,55 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
|||||||
let new_mode = mode | 0o111;
|
let new_mode = mode | 0o111;
|
||||||
permissions.set_mode(new_mode);
|
permissions.set_mode(new_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
// On non-Unix platforms, we can't set executable bit directly
|
// On non-Unix platforms, we can't set executable bit directly
|
||||||
// Just return success with a warning
|
// Just return success with a warning
|
||||||
return Ok(format!("Made {} executable (note: non-Unix platform, may not be fully supported)", path));
|
return Ok(format!(
|
||||||
|
"Made {} executable (note: non-Unix platform, may not be fully supported)",
|
||||||
|
path
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the new permissions
|
// Apply the new permissions
|
||||||
fs::set_permissions(path, permissions).map_err(|e|
|
fs::set_permissions(path, permissions).map_err(|e| {
|
||||||
DownloadError::CommandExecutionFailed(io::Error::new(
|
DownloadError::CommandExecutionFailed(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
format!("Failed to set executable permissions: {}", e)
|
format!("Failed to set executable permissions: {}", e),
|
||||||
))
|
))
|
||||||
)?;
|
})?;
|
||||||
|
|
||||||
Ok(format!("Made {} executable", path))
|
Ok(format!("Made {} executable", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a file and install it if it's a supported package format.
|
* Download a file and install it if it's a supported package format.
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `url` - The URL to download from
|
* * `url` - The URL to download from
|
||||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `Ok(String)` - The path where the file was saved or extracted
|
* * `Ok(String)` - The path where the file was saved or extracted
|
||||||
* * `Err(DownloadError)` - An error if the download or installation failed
|
* * `Err(DownloadError)` - An error if the download or installation failed
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
|
* ```no_run
|
||||||
|
* use sal::os::download_install;
|
||||||
|
*
|
||||||
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
* // Download and install a .deb package
|
||||||
|
* let result = download_install("https://example.com/package.deb", 100)?;
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
* // Download and install a .deb package
|
*
|
||||||
* let result = download_install("https://example.com/package.deb", 100)?;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* # Notes
|
* # Notes
|
||||||
*
|
*
|
||||||
* Currently only supports .deb packages on Debian-based systems.
|
* Currently only supports .deb packages on Debian-based systems.
|
||||||
* For other file types, it behaves the same as the download function.
|
* For other file types, it behaves the same as the download function.
|
||||||
*/
|
*/
|
||||||
@ -362,19 +431,23 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
// Extract filename from URL
|
// Extract filename from URL
|
||||||
let filename = match url.split('/').last() {
|
let filename = match url.split('/').last() {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => return Err(DownloadError::InvalidUrl("cannot extract filename".to_string()))
|
None => {
|
||||||
|
return Err(DownloadError::InvalidUrl(
|
||||||
|
"cannot extract filename".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a proper destination path
|
// Create a proper destination path
|
||||||
let dest_path = format!("/tmp/{}", filename);
|
let dest_path = format!("/tmp/{}", filename);
|
||||||
|
|
||||||
// Check if it's a compressed file that needs extraction
|
// Check if it's a compressed file that needs extraction
|
||||||
let lower_url = url.to_lowercase();
|
let lower_url = url.to_lowercase();
|
||||||
let is_archive = lower_url.ends_with(".tar.gz") ||
|
let is_archive = lower_url.ends_with(".tar.gz")
|
||||||
lower_url.ends_with(".tgz") ||
|
|| lower_url.ends_with(".tgz")
|
||||||
lower_url.ends_with(".tar") ||
|
|| lower_url.ends_with(".tar")
|
||||||
lower_url.ends_with(".zip");
|
|| lower_url.ends_with(".zip");
|
||||||
|
|
||||||
let download_result = if is_archive {
|
let download_result = if is_archive {
|
||||||
// For archives, use the directory-based download function
|
// For archives, use the directory-based download function
|
||||||
download(url, "/tmp", min_size_kb)?
|
download(url, "/tmp", min_size_kb)?
|
||||||
@ -382,13 +455,13 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
// For regular files, use the file-specific download function
|
// For regular files, use the file-specific download function
|
||||||
download_file(url, &dest_path, min_size_kb)?
|
download_file(url, &dest_path, min_size_kb)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the downloaded result is a file
|
// Check if the downloaded result is a file
|
||||||
let path = Path::new(&dest_path);
|
let path = Path::new(&dest_path);
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Ok(download_result); // Not a file, might be an extracted directory
|
return Ok(download_result); // Not a file, might be an extracted directory
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a .deb package
|
// Check if it's a .deb package
|
||||||
if dest_path.to_lowercase().ends_with(".deb") {
|
if dest_path.to_lowercase().ends_with(".deb") {
|
||||||
// Check if we're on a Debian-based platform
|
// Check if we're on a Debian-based platform
|
||||||
@ -396,26 +469,28 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg("command -v dpkg > /dev/null && command -v apt > /dev/null || test -f /etc/debian_version")
|
.arg("command -v dpkg > /dev/null && command -v apt > /dev/null || test -f /etc/debian_version")
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
match platform_check {
|
match platform_check {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(DownloadError::PlatformNotSupported(
|
return Err(DownloadError::PlatformNotSupported(
|
||||||
"Cannot install .deb package: not on a Debian-based system".to_string()
|
"Cannot install .deb package: not on a Debian-based system".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => return Err(DownloadError::PlatformNotSupported(
|
Err(_) => {
|
||||||
"Failed to check system compatibility for .deb installation".to_string()
|
return Err(DownloadError::PlatformNotSupported(
|
||||||
)),
|
"Failed to check system compatibility for .deb installation".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the .deb package non-interactively
|
// Install the .deb package non-interactively
|
||||||
println!("Installing package: {}", dest_path);
|
println!("Installing package: {}", dest_path);
|
||||||
let install_result = Command::new("sudo")
|
let install_result = Command::new("sudo")
|
||||||
.args(&["dpkg", "--install", &dest_path])
|
.args(&["dpkg", "--install", &dest_path])
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
match install_result {
|
match install_result {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
@ -424,24 +499,24 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
|||||||
let fix_deps = Command::new("sudo")
|
let fix_deps = Command::new("sudo")
|
||||||
.args(&["apt-get", "install", "-f", "-y"])
|
.args(&["apt-get", "install", "-f", "-y"])
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
if let Ok(fix_status) = fix_deps {
|
if let Ok(fix_status) = fix_deps {
|
||||||
if !fix_status.success() {
|
if !fix_status.success() {
|
||||||
return Err(DownloadError::InstallationFailed(
|
return Err(DownloadError::InstallationFailed(
|
||||||
"Failed to resolve package dependencies".to_string()
|
"Failed to resolve package dependencies".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(DownloadError::InstallationFailed(
|
return Err(DownloadError::InstallationFailed(
|
||||||
"Failed to resolve package dependencies".to_string()
|
"Failed to resolve package dependencies".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Package installation completed successfully");
|
println!("Package installation completed successfully");
|
||||||
},
|
}
|
||||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(download_result)
|
Ok(download_result)
|
||||||
}
|
}
|
||||||
|
597
src/os/fs.rs
597
src/os/fs.rs
File diff suppressed because it is too large
Load Diff
@ -794,7 +794,7 @@ pub fn query_opt_with_pool_params(
|
|||||||
/// This function sends a notification on the specified channel with the specified payload.
|
/// This function sends a notification on the specified channel with the specified payload.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::postgresclient::notify;
|
/// use sal::postgresclient::notify;
|
||||||
///
|
///
|
||||||
/// notify("my_channel", "Hello, world!").expect("Failed to send notification");
|
/// notify("my_channel", "Hello, world!").expect("Failed to send notification");
|
||||||
@ -810,7 +810,7 @@ pub fn notify(channel: &str, payload: &str) -> Result<(), PostgresError> {
|
|||||||
/// This function sends a notification on the specified channel with the specified payload using the connection pool.
|
/// This function sends a notification on the specified channel with the specified payload using the connection pool.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::postgresclient::notify_with_pool;
|
/// use sal::postgresclient::notify_with_pool;
|
||||||
///
|
///
|
||||||
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
|
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::process::Command;
|
|
||||||
use std::fmt;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
/// Error type for process management operations
|
/// Error type for process management operations
|
||||||
///
|
///
|
||||||
/// This enum represents various errors that can occur during process management
|
/// This enum represents various errors that can occur during process management
|
||||||
/// operations such as listing, finding, or killing processes.
|
/// operations such as listing, finding, or killing processes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -23,11 +23,18 @@ pub enum ProcessError {
|
|||||||
impl fmt::Display for ProcessError {
|
impl fmt::Display for ProcessError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ProcessError::CommandExecutionFailed(e) => write!(f, "Failed to execute command: {}", e),
|
ProcessError::CommandExecutionFailed(e) => {
|
||||||
|
write!(f, "Failed to execute command: {}", e)
|
||||||
|
}
|
||||||
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
ProcessError::CommandFailed(e) => write!(f, "{}", e),
|
||||||
ProcessError::NoProcessFound(pattern) => write!(f, "No processes found matching '{}'", pattern),
|
ProcessError::NoProcessFound(pattern) => {
|
||||||
ProcessError::MultipleProcessesFound(pattern, count) =>
|
write!(f, "No processes found matching '{}'", pattern)
|
||||||
write!(f, "Multiple processes ({}) found matching '{}'", count, pattern),
|
}
|
||||||
|
ProcessError::MultipleProcessesFound(pattern, count) => write!(
|
||||||
|
f,
|
||||||
|
"Multiple processes ({}) found matching '{}'",
|
||||||
|
count, pattern
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,18 +60,20 @@ pub struct ProcessInfo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a command exists in PATH.
|
* Check if a command exists in PATH.
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `cmd` - The command to check
|
* * `cmd` - The command to check
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `Option<String>` - The full path to the command if found, None otherwise
|
* * `Option<String>` - The full path to the command if found, None otherwise
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
|
* use sal::process::which;
|
||||||
|
*
|
||||||
* match which("git") {
|
* match which("git") {
|
||||||
* Some(path) => println!("Git is installed at: {}", path),
|
* Some(path) => println!("Git is installed at: {}", path),
|
||||||
* None => println!("Git is not installed"),
|
* None => println!("Git is not installed"),
|
||||||
@ -74,14 +83,12 @@ pub struct ProcessInfo {
|
|||||||
pub fn which(cmd: &str) -> Option<String> {
|
pub fn which(cmd: &str) -> Option<String> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let which_cmd = "where";
|
let which_cmd = "where";
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
let which_cmd = "which";
|
let which_cmd = "which";
|
||||||
|
|
||||||
let output = Command::new(which_cmd)
|
let output = Command::new(which_cmd).arg(cmd).output();
|
||||||
.arg(cmd)
|
|
||||||
.output();
|
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
if out.status.success() {
|
if out.status.success() {
|
||||||
@ -90,29 +97,34 @@ pub fn which(cmd: &str) -> Option<String> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => None
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kill processes matching a pattern.
|
* Kill processes matching a pattern.
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `pattern` - The pattern to match against process names
|
* * `pattern` - The pattern to match against process names
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `Ok(String)` - A success message indicating processes were killed or none were found
|
* * `Ok(String)` - A success message indicating processes were killed or none were found
|
||||||
* * `Err(ProcessError)` - An error if the kill operation failed
|
* * `Err(ProcessError)` - An error if the kill operation failed
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* // Kill all processes with "server" in their name
|
* // Kill all processes with "server" in their name
|
||||||
* let result = kill("server")?;
|
* use sal::process::kill;
|
||||||
* println!("{}", result);
|
*
|
||||||
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
* let result = kill("server")?;
|
||||||
|
* println!("{}", result);
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||||
@ -121,7 +133,7 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
{
|
{
|
||||||
// On Windows, use taskkill with wildcard support
|
// On Windows, use taskkill with wildcard support
|
||||||
let mut args = vec!["/F"]; // Force kill
|
let mut args = vec!["/F"]; // Force kill
|
||||||
|
|
||||||
if pattern.contains('*') {
|
if pattern.contains('*') {
|
||||||
// If it contains wildcards, use filter
|
// If it contains wildcards, use filter
|
||||||
args.extend(&["/FI", &format!("IMAGENAME eq {}", pattern)]);
|
args.extend(&["/FI", &format!("IMAGENAME eq {}", pattern)]);
|
||||||
@ -129,12 +141,12 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
// Otherwise use image name directly
|
// Otherwise use image name directly
|
||||||
args.extend(&["/IM", pattern]);
|
args.extend(&["/IM", pattern]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = Command::new("taskkill")
|
let output = Command::new("taskkill")
|
||||||
.args(&args)
|
.args(&args)
|
||||||
.output()
|
.output()
|
||||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Ok("Successfully killed processes".to_string())
|
Ok("Successfully killed processes".to_string())
|
||||||
} else {
|
} else {
|
||||||
@ -144,14 +156,20 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
if stdout.contains("No tasks") {
|
if stdout.contains("No tasks") {
|
||||||
Ok("No matching processes found".to_string())
|
Ok("No matching processes found".to_string())
|
||||||
} else {
|
} else {
|
||||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", stdout)))
|
Err(ProcessError::CommandFailed(format!(
|
||||||
|
"Failed to kill processes: {}",
|
||||||
|
stdout
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
Err(ProcessError::CommandFailed(format!(
|
||||||
|
"Failed to kill processes: {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
{
|
{
|
||||||
// On Unix-like systems, use pkill which has built-in pattern matching
|
// On Unix-like systems, use pkill which has built-in pattern matching
|
||||||
@ -160,7 +178,7 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
.arg(pattern)
|
.arg(pattern)
|
||||||
.output()
|
.output()
|
||||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||||
|
|
||||||
// pkill returns 0 if processes were killed, 1 if none matched
|
// pkill returns 0 if processes were killed, 1 if none matched
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Ok("Successfully killed processes".to_string())
|
Ok("Successfully killed processes".to_string())
|
||||||
@ -168,39 +186,47 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
|||||||
Ok("No matching processes found".to_string())
|
Ok("No matching processes found".to_string())
|
||||||
} else {
|
} else {
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
Err(ProcessError::CommandFailed(format!(
|
||||||
|
"Failed to kill processes: {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List processes matching a pattern (or all if pattern is empty).
|
* List processes matching a pattern (or all if pattern is empty).
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `pattern` - The pattern to match against process names (empty string for all processes)
|
* * `pattern` - The pattern to match against process names (empty string for all processes)
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `Ok(Vec<ProcessInfo>)` - A vector of process information for matching processes
|
* * `Ok(Vec<ProcessInfo>)` - A vector of process information for matching processes
|
||||||
* * `Err(ProcessError)` - An error if the list operation failed
|
* * `Err(ProcessError)` - An error if the list operation failed
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* // List all processes
|
* // List all processes
|
||||||
* let processes = process_list("")?;
|
* use sal::process::process_list;
|
||||||
*
|
*
|
||||||
* // List processes with "server" in their name
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
* let processes = process_list("server")?;
|
* let processes = process_list("")?;
|
||||||
* for proc in processes {
|
*
|
||||||
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
* // List processes with "server" in their name
|
||||||
|
* let processes = process_list("server")?;
|
||||||
|
* for proc in processes {
|
||||||
|
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
||||||
|
* }
|
||||||
|
* Ok(())
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||||
let mut processes = Vec::new();
|
let mut processes = Vec::new();
|
||||||
|
|
||||||
// Platform specific implementations
|
// Platform specific implementations
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
@ -209,22 +235,23 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
.args(&["process", "list", "brief"])
|
.args(&["process", "list", "brief"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
||||||
// Parse output (assuming format: Handle Name Priority)
|
// Parse output (assuming format: Handle Name Priority)
|
||||||
for line in stdout.lines().skip(1) { // Skip header
|
for line in stdout.lines().skip(1) {
|
||||||
|
// Skip header
|
||||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||||
let name = parts[1].to_string();
|
let name = parts[1].to_string();
|
||||||
|
|
||||||
// Filter by pattern if provided
|
// Filter by pattern if provided
|
||||||
if !pattern.is_empty() && !name.contains(pattern) {
|
if !pattern.is_empty() && !name.contains(pattern) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
processes.push(ProcessInfo {
|
processes.push(ProcessInfo {
|
||||||
pid,
|
pid,
|
||||||
name,
|
name,
|
||||||
@ -235,10 +262,13 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
return Err(ProcessError::CommandFailed(format!(
|
||||||
|
"Failed to list processes: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
{
|
{
|
||||||
// Unix implementation using ps
|
// Unix implementation using ps
|
||||||
@ -246,22 +276,23 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
.args(&["-eo", "pid,comm"])
|
.args(&["-eo", "pid,comm"])
|
||||||
.output()
|
.output()
|
||||||
.map_err(ProcessError::CommandExecutionFailed)?;
|
.map_err(ProcessError::CommandExecutionFailed)?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
|
||||||
// Parse output (assuming format: PID COMMAND)
|
// Parse output (assuming format: PID COMMAND)
|
||||||
for line in stdout.lines().skip(1) { // Skip header
|
for line in stdout.lines().skip(1) {
|
||||||
|
// Skip header
|
||||||
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||||
let name = parts[1].to_string();
|
let name = parts[1].to_string();
|
||||||
|
|
||||||
// Filter by pattern if provided
|
// Filter by pattern if provided
|
||||||
if !pattern.is_empty() && !name.contains(pattern) {
|
if !pattern.is_empty() && !name.contains(pattern) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
processes.push(ProcessInfo {
|
processes.push(ProcessInfo {
|
||||||
pid,
|
pid,
|
||||||
name,
|
name,
|
||||||
@ -272,38 +303,49 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
return Err(ProcessError::CommandFailed(format!("Failed to list processes: {}", stderr)));
|
return Err(ProcessError::CommandFailed(format!(
|
||||||
|
"Failed to list processes: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(processes)
|
Ok(processes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single process matching the pattern (error if 0 or more than 1 match).
|
* Get a single process matching the pattern (error if 0 or more than 1 match).
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `pattern` - The pattern to match against process names
|
* * `pattern` - The pattern to match against process names
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `Ok(ProcessInfo)` - Information about the matching process
|
* * `Ok(ProcessInfo)` - Information about the matching process
|
||||||
* * `Err(ProcessError)` - An error if no process or multiple processes match
|
* * `Err(ProcessError)` - An error if no process or multiple processes match
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```no_run
|
||||||
* let process = process_get("unique-server-name")?;
|
* use sal::process::process_get;
|
||||||
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
*
|
||||||
|
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
* let process = process_get("unique-server-name")?;
|
||||||
|
* println!("Found process: {} (PID: {})", process.name, process.pid);
|
||||||
|
* Ok(())
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
||||||
let processes = process_list(pattern)?;
|
let processes = process_list(pattern)?;
|
||||||
|
|
||||||
match processes.len() {
|
match processes.len() {
|
||||||
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
||||||
1 => Ok(processes[0].clone()),
|
1 => Ok(processes[0].clone()),
|
||||||
_ => Err(ProcessError::MultipleProcessesFound(pattern.to_string(), processes.len())),
|
_ => Err(ProcessError::MultipleProcessesFound(
|
||||||
|
pattern.to_string(),
|
||||||
|
processes.len(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ pub use os::copy as os_copy;
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
/// use sal::rhai;
|
/// use sal::rhai;
|
||||||
///
|
///
|
||||||
@ -124,7 +124,8 @@ pub use os::copy as os_copy;
|
|||||||
/// rhai::register(&mut engine);
|
/// rhai::register(&mut engine);
|
||||||
///
|
///
|
||||||
/// // Now you can use SAL functions in Rhai scripts
|
/// // Now you can use SAL functions in Rhai scripts
|
||||||
/// let result = engine.eval::<bool>("exist('some_file.txt')").unwrap();
|
/// // You can evaluate Rhai scripts with SAL functions
|
||||||
|
/// let result = engine.eval::<i64>("exist('some_file.txt')").unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
// Register OS module functions
|
// Register OS module functions
|
||||||
|
@ -1,30 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
* Dedent a multiline string by removing common leading whitespace.
|
* Dedent a multiline string by removing common leading whitespace.
|
||||||
*
|
*
|
||||||
* This function analyzes all non-empty lines in the input text to determine
|
* This function analyzes all non-empty lines in the input text to determine
|
||||||
* the minimum indentation level, then removes that amount of whitespace
|
* the minimum indentation level, then removes that amount of whitespace
|
||||||
* from the beginning of each line. This is useful for working with
|
* from the beginning of each line. This is useful for working with
|
||||||
* multi-line strings in code that have been indented to match the
|
* multi-line strings in code that have been indented to match the
|
||||||
* surrounding code structure.
|
* surrounding code structure.
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `text` - The multiline string to dedent
|
* * `text` - The multiline string to dedent
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `String` - The dedented string
|
* * `String` - The dedented string
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
|
* use sal::text::dedent;
|
||||||
|
*
|
||||||
* let indented = " line 1\n line 2\n line 3";
|
* let indented = " line 1\n line 2\n line 3";
|
||||||
* let dedented = dedent(indented);
|
* let dedented = dedent(indented);
|
||||||
* assert_eq!(dedented, "line 1\nline 2\n line 3");
|
* assert_eq!(dedented, "line 1\nline 2\n line 3");
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* # Notes
|
* # Notes
|
||||||
*
|
*
|
||||||
* - Empty lines are preserved but have all leading whitespace removed
|
* - Empty lines are preserved but have all leading whitespace removed
|
||||||
* - Tabs are counted as 4 spaces for indentation purposes
|
* - Tabs are counted as 4 spaces for indentation purposes
|
||||||
*/
|
*/
|
||||||
@ -32,7 +34,8 @@ pub fn dedent(text: &str) -> String {
|
|||||||
let lines: Vec<&str> = text.lines().collect();
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
|
||||||
// Find the minimum indentation level (ignore empty lines)
|
// Find the minimum indentation level (ignore empty lines)
|
||||||
let min_indent = lines.iter()
|
let min_indent = lines
|
||||||
|
.iter()
|
||||||
.filter(|line| !line.trim().is_empty())
|
.filter(|line| !line.trim().is_empty())
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
let mut spaces = 0;
|
let mut spaces = 0;
|
||||||
@ -51,7 +54,8 @@ pub fn dedent(text: &str) -> String {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
// Remove that many spaces from the beginning of each line
|
// Remove that many spaces from the beginning of each line
|
||||||
lines.iter()
|
lines
|
||||||
|
.iter()
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
@ -59,22 +63,22 @@ pub fn dedent(text: &str) -> String {
|
|||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut chars = line.chars().peekable();
|
let mut chars = line.chars().peekable();
|
||||||
|
|
||||||
// Skip initial spaces up to min_indent
|
// Skip initial spaces up to min_indent
|
||||||
while count < min_indent && chars.peek().is_some() {
|
while count < min_indent && chars.peek().is_some() {
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some(' ') => {
|
Some(' ') => {
|
||||||
chars.next();
|
chars.next();
|
||||||
count += 1;
|
count += 1;
|
||||||
},
|
}
|
||||||
Some('\t') => {
|
Some('\t') => {
|
||||||
chars.next();
|
chars.next();
|
||||||
count += 4;
|
count += 4;
|
||||||
},
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the remaining characters
|
// Return the remaining characters
|
||||||
chars.collect::<String>()
|
chars.collect::<String>()
|
||||||
})
|
})
|
||||||
@ -82,24 +86,25 @@ pub fn dedent(text: &str) -> String {
|
|||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix a multiline string with a specified prefix.
|
* Prefix a multiline string with a specified prefix.
|
||||||
*
|
*
|
||||||
* This function adds the specified prefix to the beginning of each line in the input text.
|
* This function adds the specified prefix to the beginning of each line in the input text.
|
||||||
*
|
*
|
||||||
* # Arguments
|
* # Arguments
|
||||||
*
|
*
|
||||||
* * `text` - The multiline string to prefix
|
* * `text` - The multiline string to prefix
|
||||||
* * `prefix` - The prefix to add to each line
|
* * `prefix` - The prefix to add to each line
|
||||||
*
|
*
|
||||||
* # Returns
|
* # Returns
|
||||||
*
|
*
|
||||||
* * `String` - The prefixed string
|
* * `String` - The prefixed string
|
||||||
*
|
*
|
||||||
* # Examples
|
* # Examples
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
|
* use sal::text::prefix;
|
||||||
|
*
|
||||||
* let text = "line 1\nline 2\nline 3";
|
* let text = "line 1\nline 2\nline 3";
|
||||||
* let prefixed = prefix(text, " ");
|
* let prefixed = prefix(text, " ");
|
||||||
* assert_eq!(prefixed, " line 1\n line 2\n line 3");
|
* assert_eq!(prefixed, " line 1\n line 2\n line 3");
|
||||||
|
@ -32,7 +32,7 @@ impl TemplateBuilder {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn open<P: AsRef<Path>>(template_path: P) -> io::Result<Self> {
|
pub fn open<P: AsRef<Path>>(template_path: P) -> io::Result<Self> {
|
||||||
let path_str = template_path.as_ref().to_string_lossy().to_string();
|
let path_str = template_path.as_ref().to_string_lossy().to_string();
|
||||||
|
|
||||||
// Verify the template file exists
|
// Verify the template file exists
|
||||||
if !Path::new(&path_str).exists() {
|
if !Path::new(&path_str).exists() {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
@ -40,14 +40,14 @@ impl TemplateBuilder {
|
|||||||
format!("Template file not found: {}", path_str),
|
format!("Template file not found: {}", path_str),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
template_path: path_str,
|
template_path: path_str,
|
||||||
context: Context::new(),
|
context: Context::new(),
|
||||||
tera: None,
|
tera: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a variable to the template context.
|
/// Adds a variable to the template context.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -61,12 +61,15 @@ impl TemplateBuilder {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::text::TemplateBuilder;
|
/// use sal::text::TemplateBuilder;
|
||||||
///
|
///
|
||||||
/// let builder = TemplateBuilder::open("templates/example.html")?
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// .add_var("title", "Hello World")
|
/// let builder = TemplateBuilder::open("templates/example.html")?
|
||||||
/// .add_var("username", "John Doe");
|
/// .add_var("title", "Hello World")
|
||||||
|
/// .add_var("username", "John Doe");
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_var<S, V>(mut self, name: S, value: V) -> Self
|
pub fn add_var<S, V>(mut self, name: S, value: V) -> Self
|
||||||
where
|
where
|
||||||
@ -76,7 +79,7 @@ impl TemplateBuilder {
|
|||||||
self.context.insert(name.as_ref(), &value);
|
self.context.insert(name.as_ref(), &value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds multiple variables to the template context from a HashMap.
|
/// Adds multiple variables to the template context from a HashMap.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -89,16 +92,19 @@ impl TemplateBuilder {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::text::TemplateBuilder;
|
/// use sal::text::TemplateBuilder;
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// let mut vars = HashMap::new();
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// vars.insert("title", "Hello World");
|
/// let mut vars = HashMap::new();
|
||||||
/// vars.insert("username", "John Doe");
|
/// vars.insert("title", "Hello World");
|
||||||
|
/// vars.insert("username", "John Doe");
|
||||||
///
|
///
|
||||||
/// let builder = TemplateBuilder::open("templates/example.html")?
|
/// let builder = TemplateBuilder::open("templates/example.html")?
|
||||||
/// .add_vars(vars);
|
/// .add_vars(vars);
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_vars<S, V>(mut self, vars: HashMap<S, V>) -> Self
|
pub fn add_vars<S, V>(mut self, vars: HashMap<S, V>) -> Self
|
||||||
where
|
where
|
||||||
@ -110,7 +116,7 @@ impl TemplateBuilder {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the Tera template engine with the template file.
|
/// Initializes the Tera template engine with the template file.
|
||||||
///
|
///
|
||||||
/// This method is called automatically by render() if not called explicitly.
|
/// This method is called automatically by render() if not called explicitly.
|
||||||
@ -122,24 +128,24 @@ impl TemplateBuilder {
|
|||||||
if self.tera.is_none() {
|
if self.tera.is_none() {
|
||||||
// Create a new Tera instance with just this template
|
// Create a new Tera instance with just this template
|
||||||
let mut tera = Tera::default();
|
let mut tera = Tera::default();
|
||||||
|
|
||||||
// Read the template content
|
// Read the template content
|
||||||
let template_content = fs::read_to_string(&self.template_path)
|
let template_content = fs::read_to_string(&self.template_path)
|
||||||
.map_err(|e| tera::Error::msg(format!("Failed to read template file: {}", e)))?;
|
.map_err(|e| tera::Error::msg(format!("Failed to read template file: {}", e)))?;
|
||||||
|
|
||||||
// Add the template to Tera
|
// Add the template to Tera
|
||||||
let template_name = Path::new(&self.template_path)
|
let template_name = Path::new(&self.template_path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or("template");
|
.unwrap_or("template");
|
||||||
|
|
||||||
tera.add_raw_template(template_name, &template_content)?;
|
tera.add_raw_template(template_name, &template_content)?;
|
||||||
self.tera = Some(tera);
|
self.tera = Some(tera);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the template with the current context.
|
/// Renders the template with the current context.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
@ -148,31 +154,34 @@ impl TemplateBuilder {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::text::TemplateBuilder;
|
/// use sal::text::TemplateBuilder;
|
||||||
///
|
///
|
||||||
/// let result = TemplateBuilder::open("templates/example.html")?
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// .add_var("title", "Hello World")
|
/// let result = TemplateBuilder::open("templates/example.html")?
|
||||||
/// .add_var("username", "John Doe")
|
/// .add_var("title", "Hello World")
|
||||||
/// .render()?;
|
/// .add_var("username", "John Doe")
|
||||||
|
/// .render()?;
|
||||||
///
|
///
|
||||||
/// println!("Rendered template: {}", result);
|
/// println!("Rendered template: {}", result);
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn render(&mut self) -> Result<String, tera::Error> {
|
pub fn render(&mut self) -> Result<String, tera::Error> {
|
||||||
// Initialize Tera if not already done
|
// Initialize Tera if not already done
|
||||||
self.initialize_tera()?;
|
self.initialize_tera()?;
|
||||||
|
|
||||||
// Get the template name
|
// Get the template name
|
||||||
let template_name = Path::new(&self.template_path)
|
let template_name = Path::new(&self.template_path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or("template");
|
.unwrap_or("template");
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
let tera = self.tera.as_ref().unwrap();
|
let tera = self.tera.as_ref().unwrap();
|
||||||
tera.render(template_name, &self.context)
|
tera.render(template_name, &self.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the template and writes the result to a file.
|
/// Renders the template and writes the result to a file.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -185,19 +194,25 @@ impl TemplateBuilder {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use sal::text::TemplateBuilder;
|
/// use sal::text::TemplateBuilder;
|
||||||
///
|
///
|
||||||
/// TemplateBuilder::open("templates/example.html")?
|
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// .add_var("title", "Hello World")
|
/// TemplateBuilder::open("templates/example.html")?
|
||||||
/// .add_var("username", "John Doe")
|
/// .add_var("title", "Hello World")
|
||||||
/// .render_to_file("output.html")?;
|
/// .add_var("username", "John Doe")
|
||||||
|
/// .render_to_file("output.html")?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn render_to_file<P: AsRef<Path>>(&mut self, output_path: P) -> io::Result<()> {
|
pub fn render_to_file<P: AsRef<Path>>(&mut self, output_path: P) -> io::Result<()> {
|
||||||
let rendered = self.render().map_err(|e| {
|
let rendered = self.render().map_err(|e| {
|
||||||
io::Error::new(io::ErrorKind::Other, format!("Template rendering error: {}", e))
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("Template rendering error: {}", e),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
fs::write(output_path, rendered)
|
fs::write(output_path, rendered)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,70 +222,68 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template_rendering() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_template_rendering() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Create a temporary template file
|
// Create a temporary template file
|
||||||
let temp_file = NamedTempFile::new()?;
|
let temp_file = NamedTempFile::new()?;
|
||||||
let template_content = "Hello, {{ name }}! Welcome to {{ place }}.\n";
|
let template_content = "Hello, {{ name }}! Welcome to {{ place }}.\n";
|
||||||
fs::write(temp_file.path(), template_content)?;
|
fs::write(temp_file.path(), template_content)?;
|
||||||
|
|
||||||
// Create a template builder and add variables
|
// Create a template builder and add variables
|
||||||
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
||||||
builder = builder
|
builder = builder.add_var("name", "John").add_var("place", "Rust");
|
||||||
.add_var("name", "John")
|
|
||||||
.add_var("place", "Rust");
|
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
let result = builder.render()?;
|
let result = builder.render()?;
|
||||||
assert_eq!(result, "Hello, John! Welcome to Rust.\n");
|
assert_eq!(result, "Hello, John! Welcome to Rust.\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template_with_multiple_vars() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_template_with_multiple_vars() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Create a temporary template file
|
// Create a temporary template file
|
||||||
let temp_file = NamedTempFile::new()?;
|
let temp_file = NamedTempFile::new()?;
|
||||||
let template_content = "{% if show_greeting %}Hello, {{ name }}!{% endif %}\n{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}\n";
|
let template_content = "{% if show_greeting %}Hello, {{ name }}!{% endif %}\n{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}\n";
|
||||||
fs::write(temp_file.path(), template_content)?;
|
fs::write(temp_file.path(), template_content)?;
|
||||||
|
|
||||||
// Create a template builder and add variables
|
// Create a template builder and add variables
|
||||||
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
||||||
|
|
||||||
// Add variables including a boolean and a vector
|
// Add variables including a boolean and a vector
|
||||||
builder = builder
|
builder = builder
|
||||||
.add_var("name", "Alice")
|
.add_var("name", "Alice")
|
||||||
.add_var("show_greeting", true)
|
.add_var("show_greeting", true)
|
||||||
.add_var("items", vec!["apple", "banana", "cherry"]);
|
.add_var("items", vec!["apple", "banana", "cherry"]);
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
let result = builder.render()?;
|
let result = builder.render()?;
|
||||||
assert_eq!(result, "Hello, Alice!\napple, banana, cherry\n");
|
assert_eq!(result, "Hello, Alice!\napple, banana, cherry\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template_with_hashmap_vars() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_template_with_hashmap_vars() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Create a temporary template file
|
// Create a temporary template file
|
||||||
let mut temp_file = NamedTempFile::new()?;
|
let mut temp_file = NamedTempFile::new()?;
|
||||||
writeln!(temp_file, "{{{{ greeting }}}}, {{{{ name }}}}!")?;
|
writeln!(temp_file, "{{{{ greeting }}}}, {{{{ name }}}}!")?;
|
||||||
temp_file.flush()?;
|
temp_file.flush()?;
|
||||||
|
|
||||||
// Create a HashMap of variables
|
// Create a HashMap of variables
|
||||||
let mut vars = HashMap::new();
|
let mut vars = HashMap::new();
|
||||||
vars.insert("greeting", "Hi");
|
vars.insert("greeting", "Hi");
|
||||||
vars.insert("name", "Bob");
|
vars.insert("name", "Bob");
|
||||||
|
|
||||||
// Create a template builder and add variables from HashMap
|
// Create a template builder and add variables from HashMap
|
||||||
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
||||||
builder = builder.add_vars(vars);
|
builder = builder.add_vars(vars);
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
let result = builder.render()?;
|
let result = builder.render()?;
|
||||||
assert_eq!(result, "Hi, Bob!\n");
|
assert_eq!(result, "Hi, Bob!\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
@ -279,20 +292,19 @@ mod tests {
|
|||||||
let temp_file = NamedTempFile::new()?;
|
let temp_file = NamedTempFile::new()?;
|
||||||
let template_content = "{{ message }}\n";
|
let template_content = "{{ message }}\n";
|
||||||
fs::write(temp_file.path(), template_content)?;
|
fs::write(temp_file.path(), template_content)?;
|
||||||
|
|
||||||
|
|
||||||
// Create an output file
|
// Create an output file
|
||||||
let output_file = NamedTempFile::new()?;
|
let output_file = NamedTempFile::new()?;
|
||||||
|
|
||||||
// Create a template builder, add a variable, and render to file
|
// Create a template builder, add a variable, and render to file
|
||||||
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
||||||
builder = builder.add_var("message", "This is a test");
|
builder = builder.add_var("message", "This is a test");
|
||||||
builder.render_to_file(output_file.path())?;
|
builder.render_to_file(output_file.path())?;
|
||||||
|
|
||||||
// Read the output file and verify its contents
|
// Read the output file and verify its contents
|
||||||
let content = fs::read_to_string(output_file.path())?;
|
let content = fs::read_to_string(output_file.path())?;
|
||||||
assert_eq!(content, "This is a test\n");
|
assert_eq!(content, "This is a test\n");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user