use pile_config::Label; use std::{ collections::HashMap, path::Component, sync::{Arc, OnceLock}, }; use crate::{ extract::traits::ObjectExtractor, value::{Item, PileValue}, }; pub struct FsExtractor { item: Item, output: OnceLock>, } impl FsExtractor { pub fn new(item: &Item) -> Self { Self { item: item.clone(), output: OnceLock::new(), } } fn get_inner(&self) -> Result<&HashMap, std::io::Error> { if let Some(x) = self.output.get() { return Ok(x); } let Item::File { path, .. } = &self.item else { return Ok(self.output.get_or_init(HashMap::new)); }; #[expect(clippy::unwrap_used)] let output = HashMap::from([ ( Label::new("extension").unwrap(), path.extension() .and_then(|x| x.to_str()) .map(|x| PileValue::String(Arc::new(x.into()))) .unwrap_or(PileValue::Null), ), ( Label::new("path").unwrap(), path.to_str() .map(|x| PileValue::String(Arc::new(x.into()))) .unwrap_or(PileValue::Null), ), ( Label::new("segments").unwrap(), path.components() .map(|x| match x { Component::CurDir => Some(".".to_owned()), Component::Normal(x) => x.to_str().map(|x| x.to_owned()), Component::ParentDir => Some("..".to_owned()), Component::RootDir => Some("/".to_owned()), Component::Prefix(x) => x.as_os_str().to_str().map(|x| x.to_owned()), }) .map(|x| x.map(|x| PileValue::String(Arc::new(x.into())))) .collect::>>() .map(|v| PileValue::Array(Arc::new(v))) .unwrap_or(PileValue::Null), ), ]); return Ok(self.output.get_or_init(|| output)); } } #[async_trait::async_trait] impl ObjectExtractor for FsExtractor { async fn field( &self, name: &Label, args: Option<&str>, ) -> Result, std::io::Error> { if args.is_some() { return Ok(None); } Ok(self.get_inner()?.get(name).cloned()) } async fn fields(&self) -> Result, std::io::Error> { Ok(self.get_inner()?.keys().cloned().collect()) } }