Compare commits
2 Commits
developmen
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
8285fdb7b9 | ||
|
1ebd591f19 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -23,3 +23,7 @@ Cargo.lock
|
||||
/rhai_test_download
|
||||
/rhai_test_fs
|
||||
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::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// Define a custom error type for download operations
|
||||
#[derive(Debug)]
|
||||
@ -26,11 +26,17 @@ pub enum DownloadError {
|
||||
impl fmt::Display for DownloadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
||||
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::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
||||
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
||||
@ -74,12 +80,18 @@ impl Error for DownloadError {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::download;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // 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
|
||||
@ -95,7 +107,11 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
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
|
||||
@ -107,7 +123,14 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, file_path);
|
||||
let output = Command::new("curl")
|
||||
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
@ -122,11 +145,17 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
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 {
|
||||
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!"),
|
||||
}
|
||||
|
||||
@ -142,10 +171,10 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz") ||
|
||||
lower_url.ends_with(".tgz") ||
|
||||
lower_url.ends_with(".tar") ||
|
||||
lower_url.ends_with(".zip");
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
if is_archive {
|
||||
// Extract the file using the appropriate command with progress indication
|
||||
@ -167,9 +196,11 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
match output {
|
||||
Ok(status) => {
|
||||
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)),
|
||||
}
|
||||
|
||||
@ -178,7 +209,7 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
Ok(entries) => {
|
||||
let count = entries.count();
|
||||
println!("Extraction complete! Extracted {} files/directories", count);
|
||||
},
|
||||
}
|
||||
Err(_) => println!("Extraction complete!"),
|
||||
}
|
||||
|
||||
@ -210,12 +241,18 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::download_file;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // 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> {
|
||||
@ -231,7 +268,14 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, dest);
|
||||
let output = Command::new("curl")
|
||||
.args(&["--progress-bar", "--location", "--fail", "--output", &temp_path, url])
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
@ -246,11 +290,17 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
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 {
|
||||
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!"),
|
||||
}
|
||||
|
||||
@ -284,9 +334,14 @@ pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String,
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::chmod_exec;
|
||||
*
|
||||
* 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> {
|
||||
@ -294,11 +349,17 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||
|
||||
// Check if the path exists and is a file
|
||||
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() {
|
||||
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
|
||||
@ -319,16 +380,19 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||
{
|
||||
// On non-Unix platforms, we can't set executable bit directly
|
||||
// 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
|
||||
fs::set_permissions(path, permissions).map_err(|e|
|
||||
fs::set_permissions(path, permissions).map_err(|e| {
|
||||
DownloadError::CommandExecutionFailed(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to set executable permissions: {}", e)
|
||||
format!("Failed to set executable permissions: {}", e),
|
||||
))
|
||||
)?;
|
||||
})?;
|
||||
|
||||
Ok(format!("Made {} executable", path))
|
||||
}
|
||||
@ -348,9 +412,14 @@ pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||
*
|
||||
* # 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(())
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # Notes
|
||||
@ -362,7 +431,11 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
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
|
||||
@ -370,10 +443,10 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz") ||
|
||||
lower_url.ends_with(".tgz") ||
|
||||
lower_url.ends_with(".tar") ||
|
||||
lower_url.ends_with(".zip");
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
let download_result = if is_archive {
|
||||
// For archives, use the directory-based download function
|
||||
@ -401,13 +474,15 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
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(
|
||||
"Failed to check system compatibility for .deb installation".to_string()
|
||||
)),
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(DownloadError::PlatformNotSupported(
|
||||
"Failed to check system compatibility for .deb installation".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Install the .deb package non-interactively
|
||||
@ -428,17 +503,17 @@ pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadE
|
||||
if let Ok(fix_status) = fix_deps {
|
||||
if !fix_status.success() {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string()
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string()
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
println!("Package installation completed successfully");
|
||||
},
|
||||
}
|
||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||
}
|
||||
}
|
||||
|
235
src/os/fs.rs
235
src/os/fs.rs
@ -1,9 +1,9 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
|
||||
// Define a custom error type for file system operations
|
||||
#[derive(Debug)]
|
||||
@ -33,14 +33,18 @@ impl fmt::Display for FsError {
|
||||
match self {
|
||||
FsError::DirectoryNotFound(dir) => write!(f, "Directory '{}' does not exist", dir),
|
||||
FsError::FileNotFound(pattern) => write!(f, "No files found matching '{}'", pattern),
|
||||
FsError::CreateDirectoryFailed(e) => write!(f, "Failed to create parent directories: {}", e),
|
||||
FsError::CreateDirectoryFailed(e) => {
|
||||
write!(f, "Failed to create parent directories: {}", e)
|
||||
}
|
||||
FsError::CopyFailed(e) => write!(f, "Failed to copy file: {}", e),
|
||||
FsError::DeleteFailed(e) => write!(f, "Failed to delete: {}", e),
|
||||
FsError::CommandFailed(e) => write!(f, "{}", e),
|
||||
FsError::CommandNotFound(e) => write!(f, "Command not found: {}", e),
|
||||
FsError::CommandExecutionError(e) => write!(f, "Failed to execute command: {}", e),
|
||||
FsError::InvalidGlobPattern(e) => write!(f, "Invalid glob pattern: {}", e),
|
||||
FsError::NotADirectory(path) => write!(f, "Path '{}' exists but is not a directory", path),
|
||||
FsError::NotADirectory(path) => {
|
||||
write!(f, "Path '{}' exists but is not a directory", path)
|
||||
}
|
||||
FsError::NotAFile(path) => write!(f, "Path '{}' is not a regular file", path),
|
||||
FsError::UnknownFileType(path) => write!(f, "Unknown file type at '{}'", path),
|
||||
FsError::MetadataError(e) => write!(f, "Failed to get file metadata: {}", e),
|
||||
@ -86,7 +90,10 @@ impl Error for FsError {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::copy;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Copy a single file
|
||||
* let result = copy("file.txt", "backup/file.txt")?;
|
||||
*
|
||||
@ -95,6 +102,9 @@ impl Error for FsError {
|
||||
*
|
||||
* // Copy a directory recursively
|
||||
* let result = copy("src_dir", "dest_dir")?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
@ -110,9 +120,7 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
// Use glob to expand wildcards
|
||||
let entries = glob::glob(src).map_err(FsError::InvalidGlobPattern)?;
|
||||
|
||||
let paths: Vec<_> = entries
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
let paths: Vec<_> = entries.filter_map(Result::ok).collect();
|
||||
|
||||
if paths.is_empty() {
|
||||
return Err(FsError::FileNotFound(src.to_string()));
|
||||
@ -150,16 +158,23 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
// For directories, use platform-specific command
|
||||
#[cfg(target_os = "windows")]
|
||||
let output = Command::new("xcopy")
|
||||
.args(&["/E", "/I", "/H", "/Y",
|
||||
.args(&[
|
||||
"/E",
|
||||
"/I",
|
||||
"/H",
|
||||
"/Y",
|
||||
&path.to_string_lossy(),
|
||||
&target_path.to_string_lossy()])
|
||||
&target_path.to_string_lossy(),
|
||||
])
|
||||
.status();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let output = Command::new("cp")
|
||||
.args(&["-R",
|
||||
.args(&[
|
||||
"-R",
|
||||
&path.to_string_lossy(),
|
||||
&target_path.to_string_lossy()])
|
||||
&target_path.to_string_lossy(),
|
||||
])
|
||||
.status();
|
||||
|
||||
match output {
|
||||
@ -167,17 +182,26 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
if status.success() {
|
||||
success_count += 1;
|
||||
}
|
||||
},
|
||||
Err(e) => println!("Warning: Failed to copy directory {}: {}", path.display(), e),
|
||||
}
|
||||
Err(e) => println!(
|
||||
"Warning: Failed to copy directory {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if success_count > 0 {
|
||||
Ok(format!("Successfully copied {} items from '{}' to '{}'",
|
||||
success_count, src, dest))
|
||||
Ok(format!(
|
||||
"Successfully copied {} items from '{}' to '{}'",
|
||||
success_count, src, dest
|
||||
))
|
||||
} else {
|
||||
Err(FsError::CommandFailed(format!("Failed to copy any files from '{}' to '{}'", src, dest)))
|
||||
Err(FsError::CommandFailed(format!(
|
||||
"Failed to copy any files from '{}' to '{}'",
|
||||
src, dest
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
// Handle non-wildcard paths normally
|
||||
@ -200,7 +224,12 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
let file_name = src_path.file_name().unwrap_or_default();
|
||||
let new_dest_path = dest_path.join(file_name);
|
||||
fs::copy(src_path, new_dest_path).map_err(FsError::CopyFailed)?;
|
||||
Ok(format!("Successfully copied file '{}' to '{}/{}'", src, dest, file_name.to_string_lossy()))
|
||||
Ok(format!(
|
||||
"Successfully copied file '{}' to '{}/{}'",
|
||||
src,
|
||||
dest,
|
||||
file_name.to_string_lossy()
|
||||
))
|
||||
} else {
|
||||
// Otherwise copy file to the specified destination
|
||||
fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
|
||||
@ -214,19 +243,23 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
.output();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let output = Command::new("cp")
|
||||
.args(&["-R", src, dest])
|
||||
.output();
|
||||
let output = Command::new("cp").args(&["-R", src, dest]).output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if out.status.success() {
|
||||
Ok(format!("Successfully copied directory '{}' to '{}'", src, dest))
|
||||
Ok(format!(
|
||||
"Successfully copied directory '{}' to '{}'",
|
||||
src, dest
|
||||
))
|
||||
} else {
|
||||
let error = String::from_utf8_lossy(&out.stderr);
|
||||
Err(FsError::CommandFailed(format!("Failed to copy directory: {}", error)))
|
||||
Err(FsError::CommandFailed(format!(
|
||||
"Failed to copy directory: {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => Err(FsError::CommandExecutionError(e)),
|
||||
}
|
||||
} else {
|
||||
@ -249,6 +282,8 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::exist;
|
||||
*
|
||||
* if exist("file.txt") {
|
||||
* println!("File exists");
|
||||
* }
|
||||
@ -273,9 +308,14 @@ pub fn exist(path: &str) -> bool {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::find_file;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let file_path = find_file("/path/to/dir", "*.txt")?;
|
||||
* println!("Found file: {}", file_path);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
||||
@ -301,7 +341,10 @@ pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
||||
_ => {
|
||||
// If multiple matches, just return the first one instead of erroring
|
||||
// This makes wildcard searches more practical
|
||||
println!("Note: Multiple files found matching '{}', returning first match", filename);
|
||||
println!(
|
||||
"Note: Multiple files found matching '{}', returning first match",
|
||||
filename
|
||||
);
|
||||
Ok(files[0].to_string_lossy().to_string())
|
||||
}
|
||||
}
|
||||
@ -322,11 +365,16 @@ pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::find_files;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let files = find_files("/path/to/dir", "*.txt")?;
|
||||
* for file in files {
|
||||
* println!("Found file: {}", file);
|
||||
* }
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
|
||||
@ -365,9 +413,14 @@ pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::find_dir;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let dir_path = find_dir("/path/to/parent", "sub*")?;
|
||||
* println!("Found directory: {}", dir_path);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
||||
@ -390,7 +443,10 @@ pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
||||
match dirs.len() {
|
||||
0 => Err(FsError::DirectoryNotFound(dirname.to_string())),
|
||||
1 => Ok(dirs[0].to_string_lossy().to_string()),
|
||||
_ => Err(FsError::CommandFailed(format!("Multiple directories found matching '{}', expected only one", dirname))),
|
||||
_ => Err(FsError::CommandFailed(format!(
|
||||
"Multiple directories found matching '{}', expected only one",
|
||||
dirname
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,11 +465,16 @@ pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::find_dirs;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let dirs = find_dirs("/path/to/parent", "sub*")?;
|
||||
* for dir in dirs {
|
||||
* println!("Found directory: {}", dir);
|
||||
* }
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
|
||||
@ -452,11 +513,17 @@ pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::delete;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Delete a file
|
||||
* let result = delete("file.txt")?;
|
||||
*
|
||||
* // Delete a directory and all its contents
|
||||
* let result = delete("directory/")?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn delete(path: &str) -> Result<String, FsError> {
|
||||
@ -494,8 +561,13 @@ pub fn delete(path: &str) -> Result<String, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::mkdir;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = mkdir("path/to/new/directory")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn mkdir(path: &str) -> Result<String, FsError> {
|
||||
@ -529,9 +601,14 @@ pub fn mkdir(path: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::file_size;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let size = file_size("file.txt")?;
|
||||
* println!("File size: {} bytes", size);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn file_size(path: &str) -> Result<i64, FsError> {
|
||||
@ -567,9 +644,14 @@ pub fn file_size(path: &str) -> Result<i64, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::rsync;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = rsync("source_dir/", "backup_dir/")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
@ -599,13 +681,17 @@ pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if out.status.success() || out.status.code() == Some(1) { // rsync and robocopy return 1 for some non-error cases
|
||||
if out.status.success() || out.status.code() == Some(1) {
|
||||
// rsync and robocopy return 1 for some non-error cases
|
||||
Ok(format!("Successfully synced '{}' to '{}'", src, dest))
|
||||
} else {
|
||||
let error = String::from_utf8_lossy(&out.stderr);
|
||||
Err(FsError::CommandFailed(format!("Failed to sync directories: {}", error)))
|
||||
Err(FsError::CommandFailed(format!(
|
||||
"Failed to sync directories: {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => Err(FsError::CommandExecutionError(e)),
|
||||
}
|
||||
}
|
||||
@ -624,9 +710,14 @@ pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::chdir;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = chdir("/path/to/directory")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn chdir(path: &str) -> Result<String, FsError> {
|
||||
@ -662,9 +753,14 @@ pub fn chdir(path: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::file_read;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let content = file_read("file.txt")?;
|
||||
* println!("File content: {}", content);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn file_read(path: &str) -> Result<String, FsError> {
|
||||
@ -700,8 +796,13 @@ pub fn file_read(path: &str) -> Result<String, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::file_write;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = file_write("file.txt", "Hello, world!")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
|
||||
@ -734,8 +835,13 @@ pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::file_write_append;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = file_write_append("log.txt", "New log entry\n")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
||||
@ -755,7 +861,8 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
||||
|
||||
// Append content to file
|
||||
use std::io::Write;
|
||||
file.write_all(content.as_bytes()).map_err(FsError::AppendFailed)?;
|
||||
file.write_all(content.as_bytes())
|
||||
.map_err(FsError::AppendFailed)?;
|
||||
|
||||
Ok(format!("Successfully appended to file '{}'", path))
|
||||
}
|
||||
@ -775,7 +882,10 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::os::mv;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Move a file
|
||||
* let result = mv("file.txt", "new_location/file.txt")?;
|
||||
*
|
||||
@ -784,6 +894,9 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
|
||||
*
|
||||
* // Rename a file
|
||||
* let result = mv("old_name.txt", "new_name.txt")?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
@ -826,7 +939,7 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
return FsError::DeleteFailed(del_err);
|
||||
}
|
||||
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||
},
|
||||
}
|
||||
Err(copy_err) => return FsError::CopyFailed(copy_err),
|
||||
}
|
||||
} else if src_path.is_dir() {
|
||||
@ -837,9 +950,7 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
.status();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let output = Command::new("cp")
|
||||
.args(&["-R", src, dest])
|
||||
.status();
|
||||
let output = Command::new("cp").args(&["-R", src, dest]).status();
|
||||
|
||||
match output {
|
||||
Ok(status) => {
|
||||
@ -850,9 +961,11 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
}
|
||||
return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
|
||||
} else {
|
||||
return FsError::CommandFailed("Failed to copy directory for move operation".to_string());
|
||||
return FsError::CommandFailed(
|
||||
"Failed to copy directory for move operation".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(cmd_err) => return FsError::CommandExecutionError(cmd_err),
|
||||
}
|
||||
}
|
||||
@ -864,7 +977,10 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
if src_path.is_file() {
|
||||
Ok(format!("Successfully moved file '{}' to '{}'", src, dest))
|
||||
} else {
|
||||
Ok(format!("Successfully moved directory '{}' to '{}'", src, dest))
|
||||
Ok(format!(
|
||||
"Successfully moved directory '{}' to '{}'",
|
||||
src, dest
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,6 +998,8 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::which;
|
||||
*
|
||||
* let cmd_path = which("ls");
|
||||
* if cmd_path != "" {
|
||||
* println!("ls is available at: {}", cmd_path);
|
||||
@ -891,14 +1009,10 @@ pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
|
||||
pub fn which(command: &str) -> String {
|
||||
// Use the appropriate command based on the platform
|
||||
#[cfg(target_os = "windows")]
|
||||
let output = Command::new("where")
|
||||
.arg(command)
|
||||
.output();
|
||||
let output = Command::new("where").arg(command).output();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let output = Command::new("which")
|
||||
.arg(command)
|
||||
.output();
|
||||
let output = Command::new("which").arg(command).output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
@ -908,7 +1022,7 @@ pub fn which(command: &str) -> String {
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(_) => String::new(),
|
||||
}
|
||||
}
|
||||
@ -929,22 +1043,31 @@ pub fn which(command: &str) -> String {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::os::cmd_ensure_exists;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Check if a single command exists
|
||||
* let result = cmd_ensure_exists("nerdctl")?;
|
||||
*
|
||||
* // Check if multiple commands exist
|
||||
* let result = cmd_ensure_exists("nerdctl,docker,containerd")?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn cmd_ensure_exists(commands: &str) -> Result<String, FsError> {
|
||||
// Split the input by commas to handle multiple commands
|
||||
let command_list: Vec<&str> = commands.split(',')
|
||||
let command_list: Vec<&str> = commands
|
||||
.split(',')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
if command_list.is_empty() {
|
||||
return Err(FsError::CommandFailed("No commands specified to check".to_string()));
|
||||
return Err(FsError::CommandFailed(
|
||||
"No commands specified to check".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut missing_commands = Vec::new();
|
||||
|
@ -794,7 +794,7 @@ pub fn query_opt_with_pool_params(
|
||||
/// This function sends a notification on the specified channel with the specified payload.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::postgresclient::notify;
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::postgresclient::notify_with_pool;
|
||||
///
|
||||
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::process::Command;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::process::Command;
|
||||
|
||||
/// Error type for process management operations
|
||||
///
|
||||
@ -23,11 +23,18 @@ pub enum ProcessError {
|
||||
impl fmt::Display for ProcessError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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::NoProcessFound(pattern) => write!(f, "No processes found matching '{}'", pattern),
|
||||
ProcessError::MultipleProcessesFound(pattern, count) =>
|
||||
write!(f, "Multiple processes ({}) found matching '{}'", count, pattern),
|
||||
ProcessError::NoProcessFound(pattern) => {
|
||||
write!(f, "No processes found matching '{}'", pattern)
|
||||
}
|
||||
ProcessError::MultipleProcessesFound(pattern, count) => write!(
|
||||
f,
|
||||
"Multiple processes ({}) found matching '{}'",
|
||||
count, pattern
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,6 +72,8 @@ pub struct ProcessInfo {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::process::which;
|
||||
*
|
||||
* match which("git") {
|
||||
* Some(path) => println!("Git is installed at: {}", path),
|
||||
* None => println!("Git is not installed"),
|
||||
@ -78,9 +87,7 @@ pub fn which(cmd: &str) -> Option<String> {
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
let which_cmd = "which";
|
||||
|
||||
let output = Command::new(which_cmd)
|
||||
.arg(cmd)
|
||||
.output();
|
||||
let output = Command::new(which_cmd).arg(cmd).output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
@ -90,8 +97,8 @@ pub fn which(cmd: &str) -> Option<String> {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(_) => None
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,8 +118,13 @@ pub fn which(cmd: &str) -> Option<String> {
|
||||
*
|
||||
* ```
|
||||
* // Kill all processes with "server" in their name
|
||||
* use sal::process::kill;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let result = kill("server")?;
|
||||
* println!("{}", result);
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
@ -144,10 +156,16 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
if stdout.contains("No tasks") {
|
||||
Ok("No matching processes found".to_string())
|
||||
} else {
|
||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", stdout)))
|
||||
Err(ProcessError::CommandFailed(format!(
|
||||
"Failed to kill processes: {}",
|
||||
stdout
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(ProcessError::CommandFailed(format!("Failed to kill processes: {}", error)))
|
||||
Err(ProcessError::CommandFailed(format!(
|
||||
"Failed to kill processes: {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,7 +186,10 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
Ok("No matching processes found".to_string())
|
||||
} else {
|
||||
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
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,6 +210,9 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
*
|
||||
* ```
|
||||
* // List all processes
|
||||
* use sal::process::process_list;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* let processes = process_list("")?;
|
||||
*
|
||||
* // List processes with "server" in their name
|
||||
@ -196,6 +220,8 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
|
||||
* for proc in processes {
|
||||
* println!("PID: {}, Name: {}", proc.pid, proc.name);
|
||||
* }
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
@ -214,7 +240,8 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
// 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();
|
||||
if parts.len() >= 2 {
|
||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||
@ -235,7 +262,10 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
}
|
||||
} else {
|
||||
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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +281,8 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
// 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();
|
||||
if parts.len() >= 2 {
|
||||
let pid = parts[0].parse::<i64>().unwrap_or(0);
|
||||
@ -272,7 +303,10 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
}
|
||||
} else {
|
||||
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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,9 +327,14 @@ pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* ```no_run
|
||||
* use sal::process::process_get;
|
||||
*
|
||||
* 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> {
|
||||
@ -304,6 +343,9 @@ pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
|
||||
match processes.len() {
|
||||
0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
|
||||
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
|
||||
///
|
||||
/// ```
|
||||
/// ```ignore
|
||||
/// use rhai::Engine;
|
||||
/// use sal::rhai;
|
||||
///
|
||||
@ -124,7 +124,8 @@ pub use os::copy as os_copy;
|
||||
/// rhai::register(&mut engine);
|
||||
///
|
||||
/// // 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>> {
|
||||
// Register OS module functions
|
||||
|
@ -18,6 +18,8 @@
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::text::dedent;
|
||||
*
|
||||
* let indented = " line 1\n line 2\n line 3";
|
||||
* let dedented = dedent(indented);
|
||||
* assert_eq!(dedented, "line 1\nline 2\n line 3");
|
||||
@ -32,7 +34,8 @@ pub fn dedent(text: &str) -> String {
|
||||
let lines: Vec<&str> = text.lines().collect();
|
||||
|
||||
// Find the minimum indentation level (ignore empty lines)
|
||||
let min_indent = lines.iter()
|
||||
let min_indent = lines
|
||||
.iter()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| {
|
||||
let mut spaces = 0;
|
||||
@ -51,7 +54,8 @@ pub fn dedent(text: &str) -> String {
|
||||
.unwrap_or(0);
|
||||
|
||||
// Remove that many spaces from the beginning of each line
|
||||
lines.iter()
|
||||
lines
|
||||
.iter()
|
||||
.map(|line| {
|
||||
if line.trim().is_empty() {
|
||||
return String::new();
|
||||
@ -66,11 +70,11 @@ pub fn dedent(text: &str) -> String {
|
||||
Some(' ') => {
|
||||
chars.next();
|
||||
count += 1;
|
||||
},
|
||||
}
|
||||
Some('\t') => {
|
||||
chars.next();
|
||||
count += 4;
|
||||
},
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
@ -82,7 +86,6 @@ pub fn dedent(text: &str) -> String {
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prefix a multiline string with a specified prefix.
|
||||
*
|
||||
@ -100,6 +103,8 @@ pub fn dedent(text: &str) -> String {
|
||||
* # Examples
|
||||
*
|
||||
* ```
|
||||
* use sal::text::prefix;
|
||||
*
|
||||
* let text = "line 1\nline 2\nline 3";
|
||||
* let prefixed = prefix(text, " ");
|
||||
* assert_eq!(prefixed, " line 1\n line 2\n line 3");
|
||||
|
@ -61,12 +61,15 @@ impl TemplateBuilder {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::text::TemplateBuilder;
|
||||
///
|
||||
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let builder = TemplateBuilder::open("templates/example.html")?
|
||||
/// .add_var("title", "Hello World")
|
||||
/// .add_var("username", "John Doe");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn add_var<S, V>(mut self, name: S, value: V) -> Self
|
||||
where
|
||||
@ -89,16 +92,19 @@ impl TemplateBuilder {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::text::TemplateBuilder;
|
||||
/// use std::collections::HashMap;
|
||||
///
|
||||
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut vars = HashMap::new();
|
||||
/// vars.insert("title", "Hello World");
|
||||
/// vars.insert("username", "John Doe");
|
||||
///
|
||||
/// let builder = TemplateBuilder::open("templates/example.html")?
|
||||
/// .add_vars(vars);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn add_vars<S, V>(mut self, vars: HashMap<S, V>) -> Self
|
||||
where
|
||||
@ -148,15 +154,18 @@ impl TemplateBuilder {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::text::TemplateBuilder;
|
||||
///
|
||||
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let result = TemplateBuilder::open("templates/example.html")?
|
||||
/// .add_var("title", "Hello World")
|
||||
/// .add_var("username", "John Doe")
|
||||
/// .render()?;
|
||||
///
|
||||
/// println!("Rendered template: {}", result);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn render(&mut self) -> Result<String, tera::Error> {
|
||||
// Initialize Tera if not already done
|
||||
@ -185,17 +194,23 @@ impl TemplateBuilder {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use sal::text::TemplateBuilder;
|
||||
///
|
||||
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// TemplateBuilder::open("templates/example.html")?
|
||||
/// .add_var("title", "Hello World")
|
||||
/// .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<()> {
|
||||
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)
|
||||
@ -217,9 +232,7 @@ mod tests {
|
||||
|
||||
// Create a template builder and add variables
|
||||
let mut builder = TemplateBuilder::open(temp_file.path())?;
|
||||
builder = builder
|
||||
.add_var("name", "John")
|
||||
.add_var("place", "Rust");
|
||||
builder = builder.add_var("name", "John").add_var("place", "Rust");
|
||||
|
||||
// Render the template
|
||||
let result = builder.render()?;
|
||||
@ -280,7 +293,6 @@ mod tests {
|
||||
let template_content = "{{ message }}\n";
|
||||
fs::write(temp_file.path(), template_content)?;
|
||||
|
||||
|
||||
// Create an output file
|
||||
let output_file = NamedTempFile::new()?;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user