diff --git a/docgen/module.hbs b/docgen/module.hbs
index 4113862..f86ce98 100644
--- a/docgen/module.hbs
+++ b/docgen/module.hbs
@@ -42,10 +42,12 @@
{{> ContentPartial content=section.body}}
+
{{/each}}
+
{{/each}}
diff --git a/talaria/src/scripting.rs b/talaria/src/scripting.rs
index a80a9ec..4a21410 100644
--- a/talaria/src/scripting.rs
+++ b/talaria/src/scripting.rs
@@ -1,6 +1,5 @@
use anyhow::{anyhow, Result};
use bincode::{Decode, Encode};
-use bytesize::Display;
use rhai::{plugin::*, Scope};
use serde::{Deserialize, Serialize};
diff --git a/talaria/src/stdlib/error.rs b/talaria/src/stdlib/error.rs
index 6905be6..eff786e 100644
--- a/talaria/src/stdlib/error.rs
+++ b/talaria/src/stdlib/error.rs
@@ -1,16 +1,89 @@
use rhai::plugin::*;
-use strum_macros::IntoStaticStr;
+use strum::EnumProperty;
+use strum_macros::Display;
+use strum_macros::{EnumProperty, IntoStaticStr};
#[export_module]
pub mod error {
- #[derive(Debug, Clone, IntoStaticStr)]
- pub enum Error {
+ #[derive(Display, Debug, Clone, IntoStaticStr, EnumProperty)]
+ pub enum ScriptError {
+ #[strum(props(class = "fs", name = "OtherFs"), to_string = "{0}")]
FsError(String),
+
+ #[strum(
+ props(class = "fs", name = "FileNotFound"),
+ to_string = "could not find file at path: \"{file_path}\""
+ )]
+ FsFileNotFound { file_path: String },
+
+ #[strum(
+ props(class = "fs", name = "DirectoryNotFound"),
+ to_string = "could not find directory at path: \"{path}\""
+ )]
+ FsDirectoryNotFound { path: String },
+
+ #[strum(
+ props(class = "fs", name = "PermissionDenied"),
+ to_string = "user does not have permission to {permission} \"{path}\""
+ )]
+ FsPermissionDenied { path: String, permission: String },
+
+ #[strum(
+ props(class = "fs", name = "FilenameTooLong"),
+ to_string = "filename \"{filename}\" is too long"
+ )]
+ FsFilenameTooLong { filename: String },
+
+ #[strum(
+ props(class = "fs", name = "IsADirectory"),
+ to_string = "path: \"{path}\" is a directory"
+ )]
+ FsIsADirectory { path: String },
+
+ #[strum(
+ props(class = "fs", name = "NotADirectory"),
+ to_string = "path: \"{path}\" is not a directory"
+ )]
+ FsNotADirectory { path: String },
+
+ #[strum(
+ props(class = "fs", name = "MalformedPath"),
+ to_string = "path: \"{path}\" is malformed"
+ )]
+ FsMalformedPath { path: String },
+
+ #[strum(
+ props(class = "fs", name = "InvalidFilename"),
+ to_string = "path: \"{file_path}\" is not a valid filename"
+ )]
+ FsInvalidFilename { file_path: String },
+
+ #[strum(
+ props(class = "fs", name = "InvalidUTF8"),
+ to_string = "file: \"{path}\" contains invalid utf-8"
+ )]
+ FsInvalidUTF8 { path: String },
+
+ #[strum(
+ props(class = "fs", name = "StorageDeviceFull"),
+ to_string = "storage device is full"
+ )]
+ FsStorageFull,
+
+ #[strum(
+ props(class = "fs", name = "ReadOnlyFilesystem"),
+ to_string = "file system is readonly"
+ )]
+ FsReadOnlyFilesystem,
+
+ #[strum(props(class = "sys"))]
SysError(String),
+
+ #[strum(props(class = "sys"))]
SysUnsupportedError(String),
}
- impl Into>> for Error {
+ impl Into>> for ScriptError {
fn into(self) -> Result> {
Err(Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(self),
@@ -19,16 +92,95 @@ pub mod error {
}
}
+ /// Returns a pretty print of the error
+ ///
+ /// # Example
+ ///
+ /// ```typescript
+ /// try {
+ /// let homework = fs::read("/home/ruby/homework/calc1.mp4");
+ /// }
+ /// catch (error) {
+ /// print(error.pretty); // `[fs] InvalidUTF8 - file: "/home/ruby/homework/calc1.mp4" contains invalid utf-8`
+ /// }
+ /// ```
+ #[rhai_fn(get = "pretty", pure)]
+ pub fn get_error_pretty(error: &mut ScriptError) -> String {
+ let class = get_error_type(error);
+ let name = get_error_name(error);
+ let message = get_error_msg(error);
+
+ format!("[{}] {} - {}", class, name, message)
+ }
+
+ /// Returns the class that this error belongs to
+ //
+ /// # Example
+ ///
+ /// ```typescript
+ /// try {
+ /// fs::remove("/"); // rm -rf :P
+ /// sys::reboot(); // it's so joever for them!!!
+ /// }
+ /// catch (error) {
+ /// switch(error.class) {
+ /// "sys" => print("there was some error relating to the sys module"),
+ /// "fs" => print("there was some error relating to the fs module"),
+ /// _ => print("some other error occurred"),
+ /// }
+ /// }
+ /// ```
+ #[rhai_fn(get = "class", pure)]
+ pub fn get_error_type(error: &mut ScriptError) -> String {
+ String::from(match error.get_str("class") {
+ Some(class) => class,
+ None => "unknown",
+ })
+ }
+
/// Returns a deterministic string that can be matched upon to identify error type
+ ///
+ /// # Example
+ ///
+ /// ```typescript
+ /// try {
+ /// let seed = fs::read("/home/coal/Important/MoneroSeed");
+ /// print("all your monero is mine!!");
+ /// print(seed);
+ /// }
+ /// catch (error) {
+ /// switch(error.name) {
+ /// "FileNotFound" => print("the monero seed does not exist"),
+ /// "PermissionDenied" => print("no permission to read the monero seed"),
+ /// "IsADirectory" => print("monero seed path is a directory"),
+ /// "InvalidUTF8" => print("monero seed does not contain valid UTF-8"),
+ /// "OtherFs" => print("some other filesystem error occurred:" + error.msg),
+ /// _ => print("some other error occurred"),
+ /// }
+ /// }
+ /// ```
#[rhai_fn(get = "name", pure)]
- pub fn get_error_name(error: &mut Error) -> String {
- let error: &'static str = error.clone().into();
- error.to_string()
+ pub fn get_error_name(error: &mut ScriptError) -> String {
+ String::from(match error.get_str("name") {
+ Some(name) => name,
+ None => "unknown",
+ })
}
- /// Returns message including both error context and message
+ /// Returns message containing human readable description of the error
+ ///
+ /// # Example
+ ///
+ /// ```typescript
+ /// try {
+ /// let homework = fs::read("/home/ruby/homework/calc1.mp4");
+ /// }
+ /// catch (error) {
+ /// print(error.msg); // `file: "/home/ruby/homework/calc1.mp4" contains invalid utf-8`
+ /// }
+ /// ```
#[rhai_fn(get = "msg", pure)]
- pub fn get_error_msg(error: &mut Error) -> String {
- format!("{:?}", error)
+ pub fn get_error_msg(error: &mut ScriptError) -> String {
+ error.to_string()
}
}
diff --git a/talaria/src/stdlib/fs.rs b/talaria/src/stdlib/fs.rs
index 536e4ef..728ccc9 100644
--- a/talaria/src/stdlib/fs.rs
+++ b/talaria/src/stdlib/fs.rs
@@ -6,28 +6,69 @@ use std::path::Path as std_Path;
/// Exposes cross-platform bindings for interacting with the filesystem
#[export_module]
pub mod fs {
+ use crate::stdlib::error::error::ScriptError;
use rhai::Array;
use rhai::Dynamic;
- use std::io::Write;
use crate::stdlib::CastArray;
/// Reads the contents of a file to a string
/// > [!CAUTION]
/// > Can throw exception if `file_path` does not exist, or isn't readable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `MalformedPath`
+ /// - `InvalidUTF8`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn read(file_path: &str) -> Result> {
let path = std_Path::new(file_path);
match std_fs::read_to_string(path) {
Ok(file_contents) => Ok(file_contents),
- Err(err) => Err(err.to_string().into()),
+ Err(err) => match err.kind() {
+ std::io::ErrorKind::NotFound => ScriptError::FsFileNotFound {
+ file_path: file_path.into(),
+ },
+ std::io::ErrorKind::PermissionDenied => ScriptError::FsPermissionDenied {
+ path: file_path.into(),
+ permission: "read".into(),
+ },
+ std::io::ErrorKind::IsADirectory => ScriptError::FsIsADirectory {
+ path: file_path.into(),
+ },
+ std::io::ErrorKind::InvalidInput => ScriptError::FsMalformedPath {
+ path: file_path.into(),
+ },
+ std::io::ErrorKind::InvalidData => ScriptError::FsInvalidUTF8 {
+ path: file_path.into(),
+ },
+ _ => ScriptError::FsError(err.to_string()),
+ }
+ .into(),
}
}
/// Read the contents of a file into an array of strings, split on newline characters
/// > [!CAUTION]
/// > Can throw exception if `file_path` does not exist, or isn't readable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `MalformedPath`
+ /// - `InvalidUTF8`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn read_lines(file_path: &str) -> Result, Box> {
read(file_path).map(|x| x.split('\n').map(|x| x.to_string().into()).collect())
@@ -38,13 +79,44 @@ pub mod fs {
/// Will create file and directory recursively if it doesn't exist
/// > [!CAUTION]
/// > Can throw exception if `file_path` isn't writable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `ReadOnlyFilesystem`
+ /// - `StorageFull`
+ /// - `InvalidFilename`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn write(file_path: &str, contents: &str) -> Result<(), Box> {
let path = std_Path::new(file_path);
match std_fs::write(path, contents) {
Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
+ Err(err) => match err.kind() {
+ // we really should never have this unless some sort of race condition occurs
+ std::io::ErrorKind::NotFound => ScriptError::FsFileNotFound {
+ file_path: file_path.into(),
+ },
+ std::io::ErrorKind::PermissionDenied => ScriptError::FsPermissionDenied {
+ path: file_path.into(),
+ permission: "write".into(),
+ },
+ std::io::ErrorKind::IsADirectory => ScriptError::FsIsADirectory {
+ path: file_path.into(),
+ },
+ std::io::ErrorKind::ReadOnlyFilesystem => ScriptError::FsReadOnlyFilesystem,
+ std::io::ErrorKind::StorageFull => ScriptError::FsStorageFull,
+ std::io::ErrorKind::InvalidFilename => ScriptError::FsInvalidFilename {
+ file_path: file_path.into(),
+ },
+ _ => ScriptError::FsError(err.to_string()),
+ }
+ .into(),
}
}
@@ -53,6 +125,18 @@ pub mod fs {
/// Will create file and directory recursively if it doesn't exist
/// > [!CAUTION]
/// > Can throw exception if `file_path` isn't writable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `ReadOnlyFilesystem`
+ /// - `StorageFull`
+ /// - `InvalidFilename`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn write_lines(file_path: &str, lines: Array) -> Result<(), Box> {
let lines = lines.try_cast::()?;
@@ -65,21 +149,23 @@ pub mod fs {
/// Will create file and directory recursively if it doesn't exist
/// > [!CAUTION]
/// > Can throw exception if `file_path` isn't writable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `ReadOnlyFilesystem`
+ /// - `StorageFull`
+ /// - `InvalidFilename`
+ /// - `InvalidUTF8`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn append(file_path: &str, content: &str) -> Result<(), Box> {
- let mut file = match std_fs::OpenOptions::new()
- .write(true)
- .append(true)
- .open(file_path)
- {
- Ok(file) => file,
- Err(err) => return Err(err.to_string().into()),
- };
-
- match file.write(content.as_bytes()) {
- Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
- }
+ let old_content = read(file_path)?;
+ write(file_path, &(old_content + content))
}
/// Appends an array of strings to a file, adding a newline after each string
@@ -87,6 +173,19 @@ pub mod fs {
/// Will create file and directory recursively if it doesn't exist
/// > [!CAUTION]
/// > Can throw exception if `file_path` isn't writable
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `ReadOnlyFilesystem`
+ /// - `StorageFull`
+ /// - `InvalidFilename`
+ /// - `InvalidUTF8`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn append_lines(file_path: &str, lines: Array) -> Result<(), Box> {
let lines = lines.try_cast::()?;
@@ -97,25 +196,49 @@ pub mod fs {
/// Removes a file or directory recursively
/// > [!CAUTION]
/// > Can throw exception if `path` can not be removed or does not exist
+ ///
+ ///
+ /// exceptions
+ ///
+ /// - `FileNotFound`
+ /// - `PermissionDenied`
+ /// - `ReadOnlyFilesystem`
+ /// - `InvalidFilename`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn remove(file_path: &str) -> Result<(), Box> {
let path = std_Path::new(file_path);
- if path.is_file() {
- return match std::fs::remove_file(path) {
- Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
- };
- }
+ let result = match (path.is_file(), path.is_dir()) {
+ (true, false) => std::fs::remove_file(path),
+ (false, true) => std::fs::remove_dir_all(path),
+ _ => {
+ return ScriptError::FsMalformedPath {
+ path: file_path.into(),
+ }
+ .into()
+ }
+ };
- if path.is_dir() {
- return match std::fs::remove_dir_all(path) {
- Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
- };
+ match result {
+ Ok(_) => Ok(()),
+ Err(err) => match err.kind() {
+ std::io::ErrorKind::NotFound => ScriptError::FsFileNotFound {
+ file_path: file_path.into(),
+ },
+ std::io::ErrorKind::PermissionDenied => ScriptError::FsPermissionDenied {
+ path: file_path.into(),
+ permission: "write".into(),
+ },
+ std::io::ErrorKind::ReadOnlyFilesystem => ScriptError::FsReadOnlyFilesystem,
+ std::io::ErrorKind::InvalidFilename => ScriptError::FsInvalidFilename {
+ file_path: file_path.into(),
+ },
+ _ => ScriptError::FsError(err.to_string().into()),
+ }
+ .into(),
}
-
- Err("Provided path is neither a file nor a directory".into())
}
/// Creates a file
@@ -123,36 +246,80 @@ pub mod fs {
/// If the parent directory does not exist, it is created recursively
/// > [!CAUTION]
/// > Can throw exception if `file_path` can not be created
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `PermissionDenied`
+ /// - `IsADirectory`
+ /// - `NotADirectory`
+ /// - `ReadOnlyFilesystem`
+ /// - `InvalidFilename`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn create(file_path: &str) -> Result<(), Box> {
let path = std_Path::new(file_path);
- let result = match path.parent() {
- Some(parent) => mkdir(parent.to_str().unwrap()),
- None => Ok(()),
- };
-
- match result {
- Ok(_) => {}
- Err(err) => return Err(err.to_string().into()),
+ match path.parent() {
+ Some(parent) => mkdir(parent.to_str().unwrap())?,
+ None => {}
};
match std_fs::File::create(file_path) {
Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
+ Err(err) => match err.kind() {
+ std::io::ErrorKind::PermissionDenied => ScriptError::FsPermissionDenied {
+ path: file_path.into(),
+ permission: "write".into(),
+ },
+ std::io::ErrorKind::IsADirectory => ScriptError::FsIsADirectory {
+ path: file_path.into(),
+ },
+ std::io::ErrorKind::NotADirectory => ScriptError::FsNotADirectory {
+ path: file_path.into(),
+ },
+ std::io::ErrorKind::ReadOnlyFilesystem => ScriptError::FsReadOnlyFilesystem,
+ std::io::ErrorKind::StorageFull => ScriptError::FsStorageFull,
+ std::io::ErrorKind::InvalidFilename => ScriptError::FsInvalidFilename {
+ file_path: file_path.into(),
+ },
+ _ => ScriptError::FsError(err.to_string().into()),
+ }
+ .into(),
}
}
/// Creates a directory recursively
/// > [!CAUTION]
/// > Can throw exception if `dir_path` can not be created
+ ///
+ ///
+ /// Exceptions
+ ///
+ /// - `PermissionDenied`
+ /// - `ReadOnlyFilesystem`
+ /// - `NotADirectory`
+ /// - `OtherFs`
+ ///
#[rhai_fn(return_raw)]
pub fn mkdir(dir_path: &str) -> Result<(), Box> {
let path = std_Path::new(dir_path);
match std_fs::create_dir_all(path) {
Ok(_) => Ok(()),
- Err(err) => Err(err.to_string().into()),
+ Err(err) => match err.kind() {
+ std::io::ErrorKind::PermissionDenied => ScriptError::FsPermissionDenied {
+ path: dir_path.into(),
+ permission: "write".into(),
+ },
+ std::io::ErrorKind::ReadOnlyFilesystem => ScriptError::FsReadOnlyFilesystem,
+ std::io::ErrorKind::NotADirectory => ScriptError::FsNotADirectory {
+ path: dir_path.into(),
+ },
+ _ => ScriptError::FsError(err.to_string()),
+ }
+ .into(),
}
}
diff --git a/talaria/src/stdlib/sys.rs b/talaria/src/stdlib/sys.rs
index 3808ab3..615e186 100644
--- a/talaria/src/stdlib/sys.rs
+++ b/talaria/src/stdlib/sys.rs
@@ -6,7 +6,7 @@ use rhai::Module;
#[export_module]
pub mod sys {
- use crate::stdlib::error::error::Error as script_error;
+ use crate::stdlib::error::error::ScriptError;
use elevated_command; // 13.2 KiB (for elevation status)
use std::{
@@ -58,7 +58,7 @@ pub mod sys {
pub fn username() -> Result> {
match whoami::fallible::username() {
Ok(res) => Ok(res),
- Err(error) => script_error::SysError(error.to_string()).into(),
+ Err(error) => ScriptError::SysError(error.to_string()).into(),
}
}
@@ -69,7 +69,7 @@ pub mod sys {
pub fn hostname() -> Result> {
match whoami::fallible::hostname() {
Ok(res) => Ok(res),
- Err(error) => script_error::SysError(error.to_string()).into(),
+ Err(error) => ScriptError::SysError(error.to_string()).into(),
}
}
@@ -109,7 +109,7 @@ pub mod sys {
"windows" => process::Command::new("shutdown").args(vec!["/r"]).status(),
_ => {
// Return when unsupported family
- return script_error::SysUnsupportedError(
+ return ScriptError::SysUnsupportedError(
format!("Unsupported family: {}", consts::FAMILY).to_string(),
)
.into();
@@ -118,7 +118,7 @@ pub mod sys {
match res {
Ok(_exit_status) => Ok(()),
- Err(error) => script_error::SysError(error.to_string()).into(),
+ Err(error) => ScriptError::SysError(error.to_string()).into(),
}
}
@@ -151,7 +151,7 @@ pub mod sys {
"windows" => process::Command::new("shutdown").args(vec!["/s"]).status(),
// Return when unsupported family
_ => {
- return script_error::SysUnsupportedError(
+ return ScriptError::SysUnsupportedError(
format!("Unsupported family: {}", consts::FAMILY).to_string(),
)
.into()
@@ -160,7 +160,7 @@ pub mod sys {
match res {
Ok(_exit_status) => Ok(()),
- Err(error) => script_error::SysError(error.to_string()).into(),
+ Err(error) => ScriptError::SysError(error.to_string()).into(),
}
}
@@ -175,23 +175,23 @@ pub mod sys {
if os_family() == "unix" {
let proc_file = match std::fs::read_to_string("/proc/uptime") {
Ok(ok) => ok,
- Err(error) => return script_error::SysError(error.to_string()).into(),
+ Err(error) => return ScriptError::SysError(error.to_string()).into(),
};
let time_vec = proc_file.trim().split(" ").collect::>();
let uptime = match time_vec.len() == 2 {
true => time_vec[0].parse::(),
- false => return script_error::SysError("uptime is malformed".to_string()).into(),
+ false => return ScriptError::SysError("uptime is malformed".to_string()).into(),
};
return match uptime {
Ok(ok) => Ok(ok),
- Err(error) => script_error::SysError(error.to_string()).into(),
+ Err(error) => ScriptError::SysError(error.to_string()).into(),
};
}
- script_error::SysUnsupportedError(
+ ScriptError::SysUnsupportedError(
format!("Unsupported family: {}", consts::FAMILY).to_string(),
)
.into()