Add file() item op and help docs
This commit is contained in:
parent
a77ae9f2ee
commit
8b7046d257
9 changed files with 128 additions and 16 deletions
|
@ -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)
|
||||
|
|
66
mps-interpreter/src/lang/vocabulary/item_ops/file.rs
Normal file
66
mps-interpreter/src/lang/vocabulary/item_ops/file.rs
Normal 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 })
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
|
|
Loading…
Reference in a new issue