Add file() item op and help docs

This commit is contained in:
NGnius (Graham) 2022-02-28 20:25:29 -05:00
parent a77ae9f2ee
commit 8b7046d257
9 changed files with 128 additions and 16 deletions

View file

@ -171,6 +171,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
.add(crate::lang::vocabulary::item_ops::ConstantItemOpFactory)
.add(crate::lang::vocabulary::item_ops::VariableAssignItemOpFactory)
.add(crate::lang::vocabulary::item_ops::FieldAssignItemOpFactory)
.add(crate::lang::vocabulary::item_ops::FileItemOpFactory)
.add(crate::lang::vocabulary::item_ops::VariableDeclareItemOpFactory)
.add(crate::lang::vocabulary::item_ops::InterpolateStringItemOpFactory)
.add(crate::lang::vocabulary::item_ops::BranchItemOpFactory)

View file

@ -0,0 +1,66 @@
use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_name, assert_token_raw, check_name};
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
#[derive(Debug)]
pub struct FileItemOp {
inner: Box<dyn MpsItemOp>,
}
impl Deref for FileItemOp {
type Target = dyn MpsItemOp;
fn deref(&self) -> &Self::Target {
self
}
}
impl Display for FileItemOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "file({})", self.inner)
}
}
impl MpsItemOp for FileItemOp {
fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> {
let inner_return = self.inner.execute(context)?;
if let MpsType::Primitive(MpsTypePrimitive::String(path)) = inner_return {
Ok(MpsType::Item(context.filesystem.single(&path, None)?))
} else {
Err(RuntimeMsg(format!(
"Cannot use {} as filepath (should be String)",
inner_return
)))
}
}
}
pub struct FileItemOpFactory;
impl MpsItemOpFactory<FileItemOp> for FileItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
!tokens.is_empty() && check_name("file", &tokens[0])
}
fn build_item_op(
&self,
tokens: &mut VecDeque<MpsToken>,
factory: &MpsItemBlockFactory,
dict: &MpsLanguageDictionary,
) -> Result<FileItemOp, SyntaxError> {
assert_name("file", tokens)?;
assert_token_raw(MpsToken::OpenBracket, tokens)?;
let end_tokens = tokens.split_off(tokens.len() - 1);
let inner_op = factory.try_build_item_statement(tokens, dict)?;
tokens.extend(end_tokens);
assert_token_raw(MpsToken::CloseBracket, tokens)?;
Ok(FileItemOp { inner: inner_op })
}
}

View file

@ -6,6 +6,7 @@ mod constant;
mod constructor;
mod empty;
mod field_assign;
mod file;
mod iter_op;
mod logical_and;
mod logical_or;
@ -26,6 +27,7 @@ pub use constant::{ConstantItemOp, ConstantItemOpFactory};
pub use constructor::{ConstructorItemOp, ConstructorItemOpFactory};
pub use empty::{EmptyItemOp, EmptyItemOpFactory};
pub use field_assign::{FieldAssignItemOp, FieldAssignItemOpFactory};
pub use file::{FileItemOp, FileItemOpFactory};
pub use iter_op::{IterItemOp, IterItemOpFactory};
pub use logical_and::{AndItemOp, AndItemOpFactory};
pub use logical_or::{OrItemOp, OrItemOpFactory};

View file

@ -38,10 +38,13 @@ impl MpsItemOp for VariableRetrieveItemOp {
let var = context.variables.get(&self.variable_name)?;
if let Some(field_name) = &self.field_name {
if let MpsType::Item(item) = var {
Ok(match item.field(field_name) {
Some(val) => MpsType::Primitive(val.clone()),
None => MpsType::empty(),
})
match item.field(field_name) {
Some(val) => Ok(MpsType::Primitive(val.clone())),
None => Err(RuntimeMsg(format!(
"Cannot access field `{}` on variable `{}` (field does not exist)",
field_name, self.variable_name
))),
}
} else {
Err(RuntimeMsg(format!(
"Cannot access field `{}` on variable `{}` ({} is not Item)",

View file

@ -230,6 +230,10 @@
//!
//! Format a value into a string. This behaves differently depending on the value's type: When the value is an Item, the item's corresponding field will replace all `{field}` instances in the format string. When the value is a primitive type (String, Int, Bool, etc.), the value's text equivalent will replace all `{}` instances in the format string. When the value is an iterable operation (Op), the operation's script equivalent will replace all `{}` instances in the format string.
//!
//! ### file(filepath) -- e.g. `file("~/Music/Romantic Traffic.flac"),`
//!
//! Load a item from file, populating the item with the song's tags.
//!
mod context;
mod interpretor;

View file

@ -81,7 +81,7 @@ impl FileIter {
root: Option<P>,
pattern: Option<&str>,
recurse: bool,
) -> Result<Self, RuntimeMsg> {
) -> Result<Self, String> {
let root_path = match root {
None => crate::lang::utility::music_folder(),
Some(p) => p.as_ref().to_path_buf(),
@ -91,7 +91,7 @@ impl FileIter {
vec.push(
root_path
.read_dir()
.map_err(|e| RuntimeMsg(format!("Directory read error: {}", e)))?
.map_err(|e| format!("Directory read error: {}", e))?
.into(),
);
vec
@ -99,15 +99,12 @@ impl FileIter {
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
};
let pattern_re = if let Some(pattern) = pattern {
Some(
Regex::new(pattern)
.map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?,
)
Some(Regex::new(pattern).map_err(|e| format!("Regex compile error: {}", e))?)
} else {
None
};
let tags_re = Regex::new(DEFAULT_REGEX)
.map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?;
let tags_re =
Regex::new(DEFAULT_REGEX).map_err(|e| format!("Regex compile error: {}", e))?;
Ok(Self {
root: root_path,
pattern: pattern_re,
@ -235,6 +232,24 @@ impl FileIter {
item.set_field("filename", path_str.to_string().into());
}
fn only_once(&mut self) -> Result<MpsItem, String> {
if self.root.is_file() {
self.is_complete = true;
match self.build_item(&self.root) {
Some(item) => Ok(item),
None => Err(format!(
"Failed to populate item from file `{}`",
self.root.display()
)),
}
} else {
Err(format!(
"Cannot populate item from non-file `{}`",
self.root.display()
))
}
}
/*fn default_title(path: &Path) -> String {
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
path.file_name()
@ -312,6 +327,8 @@ pub trait MpsFilesystemQuerier: Debug {
recursive: bool,
) -> Result<FileIter, RuntimeMsg>;
fn single(&mut self, path: &str, pattern: Option<&str>) -> Result<MpsItem, RuntimeMsg>;
fn expand(&self, folder: Option<&str>) -> Result<Option<String>, RuntimeMsg> {
#[cfg(feature = "shellexpand")]
match folder {
@ -338,6 +355,12 @@ impl MpsFilesystemQuerier for MpsFilesystemExecutor {
recursive: bool,
) -> Result<FileIter, RuntimeMsg> {
let folder = self.expand(folder)?;
FileIter::new(folder, pattern, recursive)
FileIter::new(folder, pattern, recursive).map_err(RuntimeMsg)
}
fn single(&mut self, path: &str, pattern: Option<&str>) -> Result<MpsItem, RuntimeMsg> {
let path = self.expand(Some(path))?;
let mut file_iter = FileIter::new(path, pattern, false).map_err(RuntimeMsg)?;
file_iter.only_once().map_err(RuntimeMsg)
}
}

View file

@ -534,7 +534,6 @@ fn execute_retrieveitemop_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line(
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{
item.path = item.filename,
item.not_a_field,
item.new_field = 42,
item.title = item.path,
}",
@ -750,3 +749,14 @@ fn execute_uniquefilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
true,
)
}
#[test]
fn execute_fileitemop_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line(
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{
item = file(item.filename),
}",
false,
true,
)
}

View file

@ -146,4 +146,7 @@ Comma-separated procedure steps will be executed sequentially (like a for loop i
Constructor for a new item. Each function parameter defines a new field and it's value.
~`string_format` something -- e.g. ~`{filename}` item
Format a value into a string. This behaves differently depending on the value's type: When the value is an Item, the item's corresponding field will replace all `{field}` instances in the format string. When the value is a primitive type (String, Int, Bool, etc.), the value's text equivalent will replace all `{}` instances in the format string. When the value is an iterable operation (Op), the operation's script equivalent will replace all `{}` instances in the format string.";
Format a value into a string. This behaves differently depending on the value's type: When the value is an Item, the item's corresponding field will replace all `{field}` instances in the format string. When the value is a primitive type (String, Int, Bool, etc.), the value's text equivalent will replace all `{}` instances in the format string. When the value is an iterable operation (Op), the operation's script equivalent will replace all `{}` instances in the format string.
file(filepath) -- e.g. file(`~/Music/Romantic Traffic.flac`)
Load a item from file, populating the item with the song's tags.";