Compare commits

..

No commits in common. "8285fdb7b9b7b4ffd9cfdd2a23d960d75bca94b6" and "7828f82f588e829715f2d11e96b2937854cce2b7" have entirely different histories.

8 changed files with 506 additions and 768 deletions

4
.gitignore vendored
View File

@ -23,7 +23,3 @@ Cargo.lock
/rhai_test_download
/rhai_test_fs
run_rhai_tests.log
new_location
log.txt
file.txt
fix_doc*

View File

@ -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::path::Path;
use std::fs;
use std::fmt;
use std::error::Error;
use std::io;
// Define a custom error type for download operations
#[derive(Debug)]
@ -26,17 +26,11 @@ 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),
@ -80,18 +74,12 @@ impl Error for DownloadError {
*
* # Examples
*
* ```no_run
* use sal::os::download;
* ```
* // Download a file with no minimum size requirement
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
*
* 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(())
* }
* // Download a file with minimum size requirement of 100KB
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
* ```
*
* # Notes
@ -107,11 +95,7 @@ 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
@ -123,14 +107,7 @@ 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)?;
@ -145,17 +122,11 @@ 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!"),
}
@ -171,36 +142,34 @@ 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
println!("Extracting {} to {}", temp_path, dest);
let output = if lower_url.ends_with(".zip") {
Command::new("unzip")
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
.status()
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
Command::new("tar")
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
.status()
} else {
Command::new("tar")
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
.status()
};
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)),
}
@ -209,7 +178,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!"),
}
@ -241,18 +210,12 @@ pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Downl
*
* # Examples
*
* ```no_run
* use sal::os::download_file;
* ```
* // Download a file with no minimum size requirement
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
*
* 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(())
* }
* // Download a file with minimum size requirement of 100KB
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
* ```
*/
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
@ -268,14 +231,7 @@ 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)?;
@ -290,17 +246,11 @@ 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!"),
}
@ -334,14 +284,9 @@ 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(())
* }
* ```
* // Make a file executable
* chmod_exec("/path/to/file")?;
* ```
*/
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
@ -349,17 +294,11 @@ 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
@ -380,19 +319,16 @@ 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))
}
@ -412,14 +348,9 @@ 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(())
* }
* ```
* // Download and install a .deb package
* let result = download_install("https://example.com/package.deb", 100)?;
* ```
*
* # Notes
@ -431,11 +362,7 @@ 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
@ -443,10 +370,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
@ -474,15 +401,13 @@ 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
@ -503,17 +428,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)),
}
}

View File

@ -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,18 +33,14 @@ 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),
@ -90,21 +86,15 @@ impl Error for FsError {
*
* # Examples
*
* ```no_run
* use sal::os::copy;
* ```
* // Copy a single file
* let result = copy("file.txt", "backup/file.txt")?;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Copy a single file
* let result = copy("file.txt", "backup/file.txt")?;
* // Copy multiple files using wildcards
* let result = copy("*.txt", "backup/")?;
*
* // Copy multiple files using wildcards
* let result = copy("*.txt", "backup/")?;
*
* // Copy a directory recursively
* let result = copy("src_dir", "dest_dir")?;
*
* Ok(())
* }
* // Copy a directory recursively
* let result = copy("src_dir", "dest_dir")?;
* ```
*/
pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
@ -120,7 +110,9 @@ 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()));
@ -158,23 +150,16 @@ 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",
&path.to_string_lossy(),
&target_path.to_string_lossy(),
])
.args(&["/E", "/I", "/H", "/Y",
&path.to_string_lossy(),
&target_path.to_string_lossy()])
.status();
#[cfg(not(target_os = "windows"))]
let output = Command::new("cp")
.args(&[
"-R",
&path.to_string_lossy(),
&target_path.to_string_lossy(),
])
.args(&["-R",
&path.to_string_lossy(),
&target_path.to_string_lossy()])
.status();
match output {
@ -182,26 +167,17 @@ 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
@ -224,12 +200,7 @@ 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)?;
@ -243,23 +214,19 @@ 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 {
@ -282,8 +249,6 @@ pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
* # Examples
*
* ```
* use sal::os::exist;
*
* if exist("file.txt") {
* println!("File exists");
* }
@ -308,14 +273,9 @@ 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(())
* }
* ```
* let file_path = find_file("/path/to/dir", "*.txt")?;
* println!("Found file: {}", file_path);
* ```
*/
pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
@ -341,10 +301,7 @@ 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())
}
}
@ -365,15 +322,10 @@ 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(())
* ```
* let files = find_files("/path/to/dir", "*.txt")?;
* for file in files {
* println!("Found file: {}", file);
* }
* ```
*/
@ -413,14 +365,9 @@ 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(())
* }
* ```
* let dir_path = find_dir("/path/to/parent", "sub*")?;
* println!("Found directory: {}", dir_path);
* ```
*/
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
@ -443,10 +390,7 @@ 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))),
}
}
@ -465,15 +409,10 @@ 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(())
* ```
* let dirs = find_dirs("/path/to/parent", "sub*")?;
* for dir in dirs {
* println!("Found directory: {}", dir);
* }
* ```
*/
@ -513,17 +452,11 @@ pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
* # Examples
*
* ```
* use sal::os::delete;
* // Delete a file
* let result = delete("file.txt")?;
*
* 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(())
* }
* // Delete a directory and all its contents
* let result = delete("directory/")?;
* ```
*/
pub fn delete(path: &str) -> Result<String, FsError> {
@ -561,13 +494,8 @@ 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(())
* }
* let result = mkdir("path/to/new/directory")?;
* println!("{}", result);
* ```
*/
pub fn mkdir(path: &str) -> Result<String, FsError> {
@ -601,14 +529,9 @@ 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(())
* }
* ```
* let size = file_size("file.txt")?;
* println!("File size: {} bytes", size);
* ```
*/
pub fn file_size(path: &str) -> Result<i64, FsError> {
@ -644,14 +567,9 @@ 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(())
* }
* ```
* let result = rsync("source_dir/", "backup_dir/")?;
* println!("{}", result);
* ```
*/
pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
@ -681,17 +599,13 @@ 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)),
}
}
@ -710,14 +624,9 @@ 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(())
* }
* ```
* let result = chdir("/path/to/directory")?;
* println!("{}", result);
* ```
*/
pub fn chdir(path: &str) -> Result<String, FsError> {
@ -753,14 +662,9 @@ 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(())
* }
* ```
* let content = file_read("file.txt")?;
* println!("File content: {}", content);
* ```
*/
pub fn file_read(path: &str) -> Result<String, FsError> {
@ -796,13 +700,8 @@ 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(())
* }
* let result = file_write("file.txt", "Hello, world!")?;
* println!("{}", result);
* ```
*/
pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
@ -835,13 +734,8 @@ 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(())
* }
* let result = file_write_append("log.txt", "New log entry\n")?;
* println!("{}", result);
* ```
*/
pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
@ -861,8 +755,7 @@ 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))
}
@ -882,21 +775,15 @@ pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
*
* # Examples
*
* ```no_run
* use sal::os::mv;
* ```
* // Move a file
* let result = mv("file.txt", "new_location/file.txt")?;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* // Move a file
* let result = mv("file.txt", "new_location/file.txt")?;
* // Move a directory
* let result = mv("src_dir", "dest_dir")?;
*
* // Move a directory
* let result = mv("src_dir", "dest_dir")?;
*
* // Rename a file
* let result = mv("old_name.txt", "new_name.txt")?;
*
* Ok(())
* }
* // Rename a file
* let result = mv("old_name.txt", "new_name.txt")?;
* ```
*/
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
@ -939,7 +826,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() {
@ -950,7 +837,9 @@ 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) => {
@ -961,11 +850,9 @@ 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),
}
}
@ -977,10 +864,7 @@ 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))
}
}
@ -998,8 +882,6 @@ 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);
@ -1009,10 +891,14 @@ 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) => {
@ -1022,7 +908,7 @@ pub fn which(command: &str) -> String {
} else {
String::new()
}
}
},
Err(_) => String::new(),
}
}
@ -1043,31 +929,22 @@ pub fn which(command: &str) -> String {
* # Examples
*
* ```
* use sal::os::cmd_ensure_exists;
* // Check if a single command exists
* let result = cmd_ensure_exists("nerdctl")?;
*
* 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(())
* }
* // Check if multiple commands exist
* let result = cmd_ensure_exists("nerdctl,docker,containerd")?;
* ```
*/
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();

View File

@ -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");

View File

@ -1,7 +1,7 @@
use std::error::Error;
use std::fmt;
use std::io;
use std::process::Command;
use std::fmt;
use std::error::Error;
use std::io;
/// Error type for process management operations
///
@ -23,18 +23,11 @@ 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),
}
}
}
@ -72,8 +65,6 @@ 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"),
@ -87,7 +78,9 @@ 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) => {
@ -97,8 +90,8 @@ pub fn which(cmd: &str) -> Option<String> {
} else {
None
}
}
Err(_) => None,
},
Err(_) => None
}
}
@ -118,13 +111,8 @@ 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(())
* }
* let result = kill("server")?;
* println!("{}", result);
* ```
*/
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
@ -156,16 +144,10 @@ 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)))
}
}
}
@ -186,10 +168,7 @@ 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)))
}
}
}
@ -210,17 +189,12 @@ pub fn kill(pattern: &str) -> Result<String, ProcessError> {
*
* ```
* // List all processes
* use sal::process::process_list;
* let processes = process_list("")?;
*
* fn main() -> Result<(), Box<dyn std::error::Error>> {
* let processes = process_list("")?;
*
* // List processes with "server" in their name
* let processes = process_list("server")?;
* for proc in processes {
* println!("PID: {}, Name: {}", proc.pid, proc.name);
* }
* Ok(())
* // List processes with "server" in their name
* let processes = process_list("server")?;
* for proc in processes {
* println!("PID: {}, Name: {}", proc.pid, proc.name);
* }
* ```
*/
@ -240,8 +214,7 @@ 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);
@ -262,10 +235,7 @@ 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)));
}
}
@ -281,8 +251,7 @@ 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);
@ -303,10 +272,7 @@ 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)));
}
}
@ -327,14 +293,9 @@ 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(())
* }
* ```
* let process = process_get("unique-server-name")?;
* println!("Found process: {} (PID: {})", process.name, process.pid);
* ```
*/
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
@ -343,9 +304,6 @@ 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())),
}
}

View File

@ -116,7 +116,7 @@ pub use os::copy as os_copy;
///
/// # Example
///
/// ```ignore
/// ```
/// use rhai::Engine;
/// use sal::rhai;
///
@ -124,8 +124,7 @@ pub use os::copy as os_copy;
/// rhai::register(&mut engine);
///
/// // Now you can use SAL functions in Rhai scripts
/// // You can evaluate Rhai scripts with SAL functions
/// let result = engine.eval::<i64>("exist('some_file.txt')").unwrap();
/// let result = engine.eval::<bool>("exist('some_file.txt')").unwrap();
/// ```
pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register OS module functions

View File

@ -18,8 +18,6 @@
* # 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");
@ -34,8 +32,7 @@ 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;
@ -54,8 +51,7 @@ 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();
@ -70,11 +66,11 @@ pub fn dedent(text: &str) -> String {
Some(' ') => {
chars.next();
count += 1;
}
},
Some('\t') => {
chars.next();
count += 4;
}
},
_ => break,
}
}
@ -86,6 +82,7 @@ pub fn dedent(text: &str) -> String {
.join("\n")
}
/**
* Prefix a multiline string with a specified prefix.
*
@ -103,8 +100,6 @@ 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");

View File

@ -61,15 +61,12 @@ 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(())
/// }
/// let builder = TemplateBuilder::open("templates/example.html")?
/// .add_var("title", "Hello World")
/// .add_var("username", "John Doe");
/// ```
pub fn add_var<S, V>(mut self, name: S, value: V) -> Self
where
@ -92,19 +89,16 @@ 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 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(())
/// }
/// let builder = TemplateBuilder::open("templates/example.html")?
/// .add_vars(vars);
/// ```
pub fn add_vars<S, V>(mut self, vars: HashMap<S, V>) -> Self
where
@ -154,18 +148,15 @@ 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()?;
/// let result = TemplateBuilder::open("templates/example.html")?
/// .add_var("title", "Hello World")
/// .add_var("username", "John Doe")
/// .render()?;
///
/// println!("Rendered template: {}", result);
/// Ok(())
/// }
/// println!("Rendered template: {}", result);
/// ```
pub fn render(&mut self) -> Result<String, tera::Error> {
// Initialize Tera if not already done
@ -194,23 +185,17 @@ 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(())
/// }
/// TemplateBuilder::open("templates/example.html")?
/// .add_var("title", "Hello World")
/// .add_var("username", "John Doe")
/// .render_to_file("output.html")?;
/// ```
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)
@ -232,7 +217,9 @@ 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()?;
@ -293,6 +280,7 @@ mod tests {
let template_content = "{{ message }}\n";
fs::write(temp_file.path(), template_content)?;
// Create an output file
let output_file = NamedTempFile::new()?;