tinymist_project/
model.rs
use std::hash::Hash;
use std::path::PathBuf;
use ecow::EcoVec;
use tinymist_std::error::prelude::*;
use tinymist_std::{bail, ImmutPath};
use typst::diag::EcoString;
pub use task::*;
pub use tinymist_task as task;
pub const LOCK_VERSION: &str = "0.1.0-beta0";
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case", tag = "version")]
pub enum LockFileCompat {
#[serde(rename = "0.1.0-beta0")]
Version010Beta0(LockFile),
#[serde(untagged)]
Other(serde_json::Value),
}
impl LockFileCompat {
pub fn version(&self) -> Result<&str> {
match self {
LockFileCompat::Version010Beta0(..) => Ok(LOCK_VERSION),
LockFileCompat::Other(v) => v
.get("version")
.and_then(|v| v.as_str())
.context("missing version field"),
}
}
pub fn migrate(self) -> Result<LockFile> {
match self {
LockFileCompat::Version010Beta0(v) => Ok(v),
this @ LockFileCompat::Other(..) => {
bail!(
"cannot migrate from version: {}",
this.version().unwrap_or("unknown version")
)
}
}
}
}
#[derive(Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct LockFile {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub document: Vec<ProjectInput>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub task: Vec<ApplyProjectTask>,
#[serde(skip_serializing_if = "EcoVec::is_empty", default)]
pub route: EcoVec<ProjectRoute>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ProjectInput {
pub id: Id,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<ResourcePath>,
pub main: ResourcePath,
pub inputs: Vec<(String, String)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub font_paths: Vec<ResourcePath>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub system_fonts: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub package_path: Option<ResourcePath>,
#[serde(skip_serializing_if = "Option::is_none")]
pub package_cache_path: Option<ResourcePath>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ProjectMaterial {
pub root: EcoString,
pub id: Id,
pub files: Vec<ResourcePath>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ProjectPathMaterial {
pub root: EcoString,
pub id: Id,
pub files: Vec<PathBuf>,
}
impl ProjectPathMaterial {
pub fn from_deps(doc_id: Id, files: EcoVec<ImmutPath>) -> Self {
let mut files: Vec<_> = files.into_iter().map(|p| p.as_ref().to_owned()).collect();
files.sort();
ProjectPathMaterial {
root: EcoString::default(),
id: doc_id,
files,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ProjectRoute {
pub id: Id,
pub priority: u32,
}
#[cfg(test)]
mod tests {
use std::path::Path;
use tinymist_task::PathPattern;
use tinymist_world::EntryState;
use typst::syntax::VirtualPath;
use super::*;
#[test]
fn test_substitute_path() {
let root = Path::new("/root");
let entry =
EntryState::new_rooted(root.into(), Some(VirtualPath::new("/dir1/dir2/file.txt")));
assert_eq!(
PathPattern::new("/substitute/$dir/$name").substitute(&entry),
Some(PathBuf::from("/substitute/dir1/dir2/file.txt").into())
);
assert_eq!(
PathPattern::new("/substitute/$dir/../$name").substitute(&entry),
Some(PathBuf::from("/substitute/dir1/file.txt").into())
);
assert_eq!(
PathPattern::new("/substitute/$name").substitute(&entry),
Some(PathBuf::from("/substitute/file.txt").into())
);
assert_eq!(
PathPattern::new("/substitute/target/$dir/$name").substitute(&entry),
Some(PathBuf::from("/substitute/target/dir1/dir2/file.txt").into())
);
}
}