// SPDX-License-Identifier: Prosperity-3.0.0 // Copyright Scientific Computing Studio // Source: https://git.scient.ing/education/coursebank //! The on-disk layout of a course directory. //! //! Two of these directories hold fragments of the course file rather than files //! of their own kind: `lectures/` and `objectives/` are merged into one //! [`crate::course::CourseFile`] on load, along with `references.yaml`. See //! [`crate::course::fragment`]. use std::path::PathBuf; use crate::course::COURSE_FILE; use crate::error::{Error, Result}; /// The standard directory layout of a course, resolved from a root. #[derive(Debug, Clone)] pub struct Layout { /// The course root. pub root: PathBuf, } impl Layout { /// Builds a layout from a course root directory. /// /// # Arguments /// /// * `root` - the course directory. /// /// # Returns /// /// The layout. pub fn new(root: impl Into) -> Layout { Layout { root: root.into() } } /// Path to `course.yaml`. pub fn course_file(&self) -> PathBuf { self.root.join(COURSE_FILE) } /// Path to `references.yaml`, the bibliography when it is kept out of /// `course.yaml`. /// /// Optional: absent means the course keeps its `references:` section in the /// course file, which is how an unsplit course is arranged. pub fn references_file(&self) -> PathBuf { self.root.join(crate::course::fragment::REFERENCES_FILE) } /// Directory holding one file per lecture. pub fn lectures(&self) -> PathBuf { self.root.join("lectures") } /// Directory holding one file per learning objective. pub fn objectives(&self) -> PathBuf { self.root.join("objectives") } /// Directory holding item bank YAML files. pub fn banks(&self) -> PathBuf { self.root.join("banks") } /// Directory holding assessment records. pub fn assessments(&self) -> PathBuf { self.root.join("assessments") } /// Directory holding response tables and derived statistics. pub fn data(&self) -> PathBuf { self.root.join("data") } /// Directory holding generated reports. pub fn reports(&self) -> PathBuf { self.root.join("reports") } /// Directory holding generated exports such as QTI packages. pub fn build(&self) -> PathBuf { self.root.join("build") } /// Directory holding emitted JSON Schema files for editor validation. pub fn schema(&self) -> PathBuf { self.root.join("schema") } /// Directory holding Typst export templates and their configuration. /// /// Unlike the other directories, this one is *not* created by /// [`Layout::create_all`]. Its absence is meaningful: a course with no /// `templates/` directory uses the templates compiled into the binary, and /// creating an empty one on `init` would suggest a customization step is /// required when it is not. `coursebank template dump` creates it on demand. pub fn templates(&self) -> PathBuf { self.root.join("templates") } /// Directory holding sealed administrations. /// /// Deliberately not `assessments/`: /// [`crate::assessment::AssessmentFile::load_all`] parses every `.yaml` in /// that directory, and a seal is not an assessment record. pub fn seals(&self) -> PathBuf { self.root.join(crate::seal::SEAL_DIR) } /// Creates every directory in the layout. /// /// # Errors /// /// Returns [`Error::Io`] if a directory cannot be created. pub fn create_all(&self) -> Result<()> { for dir in [ self.root.clone(), self.lectures(), self.objectives(), self.banks(), self.assessments(), self.data(), self.reports(), self.build(), self.schema(), ] { std::fs::create_dir_all(&dir).map_err(|e| Error::io(&dir, e))?; } Ok(()) } }