aipack 0.8.23

Command Agent runner to accelerate production coding with genai.
use crate::dir_context::DirContext;
use crate::types::FileRef;
use mlua::{IntoLua, Lua};
use serde::{Serialize, Serializer};
use simple_fs::SPath;

/// The FileInfo object contains the metadata of a file but not its content.
/// The ctime, mtime, and size metadata are generally loaded,
/// but this can be turned off when listing files using the `with_meta = false` option.
///
/// NOTE: Just json serializer to add the _type..
#[derive(Debug)]
pub struct FileInfo {
	pub path: String,
	/// The dir/parent path of this file from path (will be empty if no parent of the rel path)
	pub dir: String,
	pub name: String,
	pub stem: String,
	pub ext: String,

	pub ctime: Option<i64>, // seconds since epoch, or nil in Lua
	pub mtime: Option<i64>,
	pub size: Option<i64>, // size in bytes
	pub is_likely_text: bool,
}

pub struct WithMeta<'a> {
	full_path: Option<&'a SPath>,
	with_meta: bool,
}
impl From<bool> for WithMeta<'_> {
	fn from(with_meta: bool) -> Self {
		WithMeta {
			full_path: None,
			with_meta,
		}
	}
}
impl<'a> From<&'a SPath> for WithMeta<'a> {
	fn from(full_path: &'a SPath) -> Self {
		WithMeta {
			full_path: Some(full_path),
			with_meta: true,
		}
	}
}

impl FileInfo {
	/// - with_meta: when true, will attempt to get the file meta. Will ignore if error
	/// - with_meta if SPath, then, it's true, and the SPath is the absolute path
	/// - `base_path` is only use with_meta true to attempt to get the meta
	pub fn new<'a>(dir_context: &DirContext, rel_path: impl Into<SPath>, with_meta: impl Into<WithMeta<'a>>) -> Self {
		let path: SPath = rel_path.into();
		// make it tild home
		let path = dir_context.maybe_home_path_into_tilde(path);

		let with_meta: WithMeta = with_meta.into();
		if with_meta.with_meta {
			// Here we preserve the ~ format if there is one
			let mut res = FileInfo::from_path(path.clone());

			// Here to resolve the ~ for the full_path.meta
			let full_path = match with_meta.full_path {
				Some(full_path) => full_path.clone(),
				None => dir_context.maybe_tilde_path_into_home(path),
			};

			if let Ok(meta) = full_path.meta() {
				res.ctime = Some(meta.created_epoch_us);
				res.mtime = Some(meta.modified_epoch_us);
				res.size = Some(meta.size as i64);
				res.is_likely_text = full_path.is_likely_text();
			}
			res
		} else {
			FileInfo::from_path(path)
		}
	}

	/// New "new" which takes from internal type file_ref (better for smeta on base_dir relative path)
	/// NOTE: Probably need to replace the current `new(...)`
	pub fn from_file_ref(dir_context: &DirContext, file_ref: FileRef) -> Self {
		let smeta = file_ref.smeta;
		// make it home
		let spath = dir_context.maybe_home_path_into_tilde(file_ref.spath.clone());

		let mut file_info = FileInfo::from_path(spath);
		if let Some(smeta) = smeta {
			file_info.ctime = Some(smeta.created_epoch_us);
			file_info.mtime = Some(smeta.modified_epoch_us);
			file_info.size = Some(smeta.size as i64);
			file_info.is_likely_text = file_ref.spath.is_likely_text();
		}

		file_info
	}

	/// Internal from spath (note: do not make public)
	fn from_path(file: SPath) -> Self {
		let dir = file.parent().map(|p| p.to_string()).unwrap_or_default();
		let is_likely_text = file.is_likely_text();
		FileInfo {
			path: file.to_string(),
			name: file.name().to_string(),
			dir,
			stem: file.stem().to_string(),
			ext: file.ext().to_string(),
			// -- when created _with_meta
			ctime: None,
			mtime: None,
			size: None,
			is_likely_text,
		}
	}
}

// region:    --- Serde Serializer

impl Serialize for FileInfo {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		use serde::ser::SerializeStruct;
		// Max 10 fields (path, dir, name, stem, ext, ctime, mtime, size, is_likely_text, _type)
		let mut state = serializer.serialize_struct("FileInfo", 10)?;

		state.serialize_field("_type", "FileInfo")?;
		state.serialize_field("path", &self.path)?;
		state.serialize_field("dir", &self.dir)?;
		state.serialize_field("name", &self.name)?;
		state.serialize_field("stem", &self.stem)?;
		state.serialize_field("ext", &self.ext)?;

		if let Some(ctime) = self.ctime {
			state.serialize_field("ctime", &ctime)?;
		}
		if let Some(mtime) = self.mtime {
			state.serialize_field("mtime", &mtime)?;
		}
		if let Some(size) = self.size {
			state.serialize_field("size", &size)?;
		}
		state.serialize_field("is_likely_text", &self.is_likely_text)?;

		state.end()
	}
}

// endregion: --- Serde Serializer

// region:    --- Lua

impl IntoLua for FileInfo {
	fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
		let table = lua.create_table()?;
		table.set("_type", "FileInfo")?;

		table.set("path", self.path)?;
		table.set("dir", self.dir)?;
		table.set("name", self.name)?;
		table.set("stem", self.stem)?;
		table.set("ext", self.ext)?;
		if let Some(ctime) = self.ctime {
			table.set("ctime", ctime)?;
		}
		if let Some(mtime) = self.mtime {
			table.set("mtime", mtime)?;
		}
		if let Some(size) = self.size {
			table.set("size", size)?;
		}
		table.set("is_likely_text", self.is_likely_text)?;
		Ok(mlua::Value::Table(table))
	}
}

// endregion: --- Lua