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::ConstantItemOpFactory)
|
||||||
.add(crate::lang::vocabulary::item_ops::VariableAssignItemOpFactory)
|
.add(crate::lang::vocabulary::item_ops::VariableAssignItemOpFactory)
|
||||||
.add(crate::lang::vocabulary::item_ops::FieldAssignItemOpFactory)
|
.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::VariableDeclareItemOpFactory)
|
||||||
.add(crate::lang::vocabulary::item_ops::InterpolateStringItemOpFactory)
|
.add(crate::lang::vocabulary::item_ops::InterpolateStringItemOpFactory)
|
||||||
.add(crate::lang::vocabulary::item_ops::BranchItemOpFactory)
|
.add(crate::lang::vocabulary::item_ops::BranchItemOpFactory)
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static> BoxedMpsOpFactory
|
||||||
tokens,
|
tokens,
|
||||||
)?;
|
)?;
|
||||||
assert_token_raw(MpsToken::OpenBracket, tokens)?;
|
assert_token_raw(MpsToken::OpenBracket, tokens)?;
|
||||||
let end_tokens = tokens.split_off(tokens.len()-1);
|
let end_tokens = tokens.split_off(tokens.len() - 1);
|
||||||
let func = self.op_factory.build_function_params(name, tokens, dict)?;
|
let func = self.op_factory.build_function_params(name, tokens, dict)?;
|
||||||
tokens.extend(end_tokens);
|
tokens.extend(end_tokens);
|
||||||
assert_token_raw(MpsToken::CloseBracket, tokens)?;
|
assert_token_raw(MpsToken::CloseBracket, tokens)?;
|
||||||
|
|
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 constructor;
|
||||||
mod empty;
|
mod empty;
|
||||||
mod field_assign;
|
mod field_assign;
|
||||||
|
mod file;
|
||||||
mod iter_op;
|
mod iter_op;
|
||||||
mod logical_and;
|
mod logical_and;
|
||||||
mod logical_or;
|
mod logical_or;
|
||||||
|
@ -26,6 +27,7 @@ pub use constant::{ConstantItemOp, ConstantItemOpFactory};
|
||||||
pub use constructor::{ConstructorItemOp, ConstructorItemOpFactory};
|
pub use constructor::{ConstructorItemOp, ConstructorItemOpFactory};
|
||||||
pub use empty::{EmptyItemOp, EmptyItemOpFactory};
|
pub use empty::{EmptyItemOp, EmptyItemOpFactory};
|
||||||
pub use field_assign::{FieldAssignItemOp, FieldAssignItemOpFactory};
|
pub use field_assign::{FieldAssignItemOp, FieldAssignItemOpFactory};
|
||||||
|
pub use file::{FileItemOp, FileItemOpFactory};
|
||||||
pub use iter_op::{IterItemOp, IterItemOpFactory};
|
pub use iter_op::{IterItemOp, IterItemOpFactory};
|
||||||
pub use logical_and::{AndItemOp, AndItemOpFactory};
|
pub use logical_and::{AndItemOp, AndItemOpFactory};
|
||||||
pub use logical_or::{OrItemOp, OrItemOpFactory};
|
pub use logical_or::{OrItemOp, OrItemOpFactory};
|
||||||
|
|
|
@ -38,10 +38,13 @@ impl MpsItemOp for VariableRetrieveItemOp {
|
||||||
let var = context.variables.get(&self.variable_name)?;
|
let var = context.variables.get(&self.variable_name)?;
|
||||||
if let Some(field_name) = &self.field_name {
|
if let Some(field_name) = &self.field_name {
|
||||||
if let MpsType::Item(item) = var {
|
if let MpsType::Item(item) = var {
|
||||||
Ok(match item.field(field_name) {
|
match item.field(field_name) {
|
||||||
Some(val) => MpsType::Primitive(val.clone()),
|
Some(val) => Ok(MpsType::Primitive(val.clone())),
|
||||||
None => MpsType::empty(),
|
None => Err(RuntimeMsg(format!(
|
||||||
})
|
"Cannot access field `{}` on variable `{}` (field does not exist)",
|
||||||
|
field_name, self.variable_name
|
||||||
|
))),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(RuntimeMsg(format!(
|
Err(RuntimeMsg(format!(
|
||||||
"Cannot access field `{}` on variable `{}` ({} is not Item)",
|
"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.
|
//! 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 context;
|
||||||
mod interpretor;
|
mod interpretor;
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl FileIter {
|
||||||
root: Option<P>,
|
root: Option<P>,
|
||||||
pattern: Option<&str>,
|
pattern: Option<&str>,
|
||||||
recurse: bool,
|
recurse: bool,
|
||||||
) -> Result<Self, RuntimeMsg> {
|
) -> Result<Self, String> {
|
||||||
let root_path = match root {
|
let root_path = match root {
|
||||||
None => crate::lang::utility::music_folder(),
|
None => crate::lang::utility::music_folder(),
|
||||||
Some(p) => p.as_ref().to_path_buf(),
|
Some(p) => p.as_ref().to_path_buf(),
|
||||||
|
@ -91,7 +91,7 @@ impl FileIter {
|
||||||
vec.push(
|
vec.push(
|
||||||
root_path
|
root_path
|
||||||
.read_dir()
|
.read_dir()
|
||||||
.map_err(|e| RuntimeMsg(format!("Directory read error: {}", e)))?
|
.map_err(|e| format!("Directory read error: {}", e))?
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
vec
|
vec
|
||||||
|
@ -99,15 +99,12 @@ impl FileIter {
|
||||||
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
|
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
|
||||||
};
|
};
|
||||||
let pattern_re = if let Some(pattern) = pattern {
|
let pattern_re = if let Some(pattern) = pattern {
|
||||||
Some(
|
Some(Regex::new(pattern).map_err(|e| format!("Regex compile error: {}", e))?)
|
||||||
Regex::new(pattern)
|
|
||||||
.map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let tags_re = Regex::new(DEFAULT_REGEX)
|
let tags_re =
|
||||||
.map_err(|e| RuntimeMsg(format!("Regex compile error: {}", e)))?;
|
Regex::new(DEFAULT_REGEX).map_err(|e| format!("Regex compile error: {}", e))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
root: root_path,
|
root: root_path,
|
||||||
pattern: pattern_re,
|
pattern: pattern_re,
|
||||||
|
@ -235,6 +232,24 @@ impl FileIter {
|
||||||
item.set_field("filename", path_str.to_string().into());
|
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 {
|
/*fn default_title(path: &Path) -> String {
|
||||||
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
|
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
|
||||||
path.file_name()
|
path.file_name()
|
||||||
|
@ -312,6 +327,8 @@ pub trait MpsFilesystemQuerier: Debug {
|
||||||
recursive: bool,
|
recursive: bool,
|
||||||
) -> Result<FileIter, RuntimeMsg>;
|
) -> 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> {
|
fn expand(&self, folder: Option<&str>) -> Result<Option<String>, RuntimeMsg> {
|
||||||
#[cfg(feature = "shellexpand")]
|
#[cfg(feature = "shellexpand")]
|
||||||
match folder {
|
match folder {
|
||||||
|
@ -338,6 +355,12 @@ impl MpsFilesystemQuerier for MpsFilesystemExecutor {
|
||||||
recursive: bool,
|
recursive: bool,
|
||||||
) -> Result<FileIter, RuntimeMsg> {
|
) -> Result<FileIter, RuntimeMsg> {
|
||||||
let folder = self.expand(folder)?;
|
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(
|
execute_single_line(
|
||||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{
|
||||||
item.path = item.filename,
|
item.path = item.filename,
|
||||||
item.not_a_field,
|
|
||||||
item.new_field = 42,
|
item.new_field = 42,
|
||||||
item.title = item.path,
|
item.title = item.path,
|
||||||
}",
|
}",
|
||||||
|
@ -750,3 +749,14 @@ fn execute_uniquefilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||||
true,
|
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.
|
Constructor for a new item. Each function parameter defines a new field and it's value.
|
||||||
|
|
||||||
~`string_format` something -- e.g. ~`{filename}` item
|
~`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