Files
coursebank/src/model/layout.rs
T
2026-09-26 01:15:12 -04:00

134 lines
4.0 KiB
Rust

// 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<PathBuf>) -> 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(())
}
}