Add ListExtractor

This commit is contained in:
2026-03-10 20:24:56 -07:00
parent 280bbcb83e
commit 48ac93c78e
22 changed files with 386 additions and 93 deletions

View File

@@ -35,7 +35,7 @@ use crate::Item;
/// Metadata is exposed as an immutable map of {label: value},
/// much like a json object.
#[async_trait::async_trait]
pub trait Extractor: Send + Sync {
pub trait ObjectExtractor: Send + Sync {
/// Get the field at `name` from `item`.
/// - returns `None` if `name` is not a valid field
/// - returns `Some(Null)` if `name` is not available
@@ -50,14 +50,31 @@ pub trait Extractor: Send + Sync {
async fn fields(&self) -> Result<Vec<Label>, std::io::Error>;
}
/// An attachment that extracts metadata from an [Item].
///
/// Metadata is exposed as an immutable list of values.
#[async_trait::async_trait]
pub trait ListExtractor: Send + Sync {
/// Get the item at index `idx`.
/// Indices start at zero, and must be consecutive.
/// - returns `None` if `idx` is out of range
/// - returns `Some(Null)` if `None` is at `idx`
async fn get<'a>(
&'a self,
idx: usize,
) -> Result<Option<&'a crate::PileValue<'a>>, std::io::Error>;
async fn len(&self) -> Result<usize, std::io::Error>;
async fn is_empty(&self) -> Result<bool, std::io::Error> {
Ok(self.len().await? == 0)
}
}
pub struct MetaExtractor<'a> {
inner: MapExtractor<'a>,
}
//
// MARK: file
//
impl<'a> MetaExtractor<'a> {
#[expect(clippy::unwrap_used)]
pub fn new(item: &'a Item) -> Self {
@@ -65,35 +82,35 @@ impl<'a> MetaExtractor<'a> {
inner: HashMap::from([
(
Label::new("flac").unwrap(),
crate::PileValue::Extractor(Arc::new(FlacExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(FlacExtractor::new(item))),
),
(
Label::new("id3").unwrap(),
crate::PileValue::Extractor(Arc::new(Id3Extractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(Id3Extractor::new(item))),
),
(
Label::new("fs").unwrap(),
crate::PileValue::Extractor(Arc::new(FsExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(FsExtractor::new(item))),
),
(
Label::new("epub").unwrap(),
crate::PileValue::Extractor(Arc::new(EpubExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(EpubExtractor::new(item))),
),
(
Label::new("exif").unwrap(),
crate::PileValue::Extractor(Arc::new(ExifExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(ExifExtractor::new(item))),
),
(
Label::new("pdf").unwrap(),
crate::PileValue::Extractor(Arc::new(PdfExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(PdfExtractor::new(item))),
),
(
Label::new("toml").unwrap(),
crate::PileValue::Extractor(Arc::new(TomlExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(TomlExtractor::new(item))),
),
(
Label::new("sidecar").unwrap(),
crate::PileValue::Extractor(Arc::new(SidecarExtractor::new(item))),
crate::PileValue::ObjectExtractor(Arc::new(SidecarExtractor::new(item))),
),
]),
};
@@ -103,7 +120,7 @@ impl<'a> MetaExtractor<'a> {
}
#[async_trait::async_trait]
impl Extractor for MetaExtractor<'_> {
impl ObjectExtractor for MetaExtractor<'_> {
async fn field<'a>(
&'a self,
name: &pile_config::Label,