Skip to main content

dsfb_computer_graphics/
outputs.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use serde::Serialize;
5
6use crate::error::{Error, Result};
7
8pub const NOTEBOOK_OUTPUT_ROOT_NAME: &str = "output-dsfb-computer-graphics";
9pub const ARTIFACT_MANIFEST_FILE_NAME: &str = "artifact_manifest.json";
10pub const PDF_BUNDLE_FILE_NAME: &str = "artifacts_bundle.pdf";
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
13pub struct RunLayout {
14    pub output_root: PathBuf,
15    pub run_dir: PathBuf,
16    pub run_name: String,
17    pub artifact_manifest_path: PathBuf,
18    pub pdf_bundle_path: PathBuf,
19    pub zip_bundle_path: PathBuf,
20}
21
22pub fn is_valid_timestamp_label(value: &str) -> bool {
23    let bytes = value.as_bytes();
24    let mut digit_count = 0usize;
25    let mut hyphen_count = 0usize;
26
27    for byte in bytes {
28        if byte.is_ascii_digit() {
29            digit_count += 1;
30        } else if *byte == b'-' || *byte == b'_' {
31            hyphen_count += 1;
32        } else {
33            return false;
34        }
35    }
36
37    !value.is_empty() && digit_count >= 12 && hyphen_count >= 1
38}
39
40pub fn format_run_directory_name(timestamp: &str) -> String {
41    format!("{NOTEBOOK_OUTPUT_ROOT_NAME}-{timestamp}")
42}
43
44pub fn format_zip_bundle_name(run_name: &str) -> String {
45    format!("{run_name}.zip")
46}
47
48pub fn artifact_manifest_path(run_dir: &Path) -> PathBuf {
49    run_dir.join(ARTIFACT_MANIFEST_FILE_NAME)
50}
51
52pub fn pdf_bundle_path(run_dir: &Path) -> PathBuf {
53    run_dir.join(PDF_BUNDLE_FILE_NAME)
54}
55
56pub fn zip_bundle_path(output_root: &Path, run_name: &str) -> PathBuf {
57    output_root.join(format_zip_bundle_name(run_name))
58}
59
60pub fn create_named_run_dir(output_root: &Path, run_name: &str) -> Result<RunLayout> {
61    fs::create_dir_all(output_root)?;
62    let run_dir = output_root.join(run_name);
63    if run_dir.exists() {
64        return Err(Error::Message(format!(
65            "refusing to overwrite existing run directory {}",
66            run_dir.display()
67        )));
68    }
69    fs::create_dir_all(&run_dir)?;
70
71    Ok(RunLayout {
72        output_root: output_root.to_path_buf(),
73        run_dir: run_dir.clone(),
74        run_name: run_name.to_string(),
75        artifact_manifest_path: artifact_manifest_path(&run_dir),
76        pdf_bundle_path: pdf_bundle_path(&run_dir),
77        zip_bundle_path: zip_bundle_path(output_root, run_name),
78    })
79}
80
81pub fn create_timestamped_run_dir(output_root: &Path, timestamp: &str) -> Result<RunLayout> {
82    if !is_valid_timestamp_label(timestamp) {
83        return Err(Error::Message(format!(
84            "timestamp label `{timestamp}` is invalid for notebook output naming"
85        )));
86    }
87
88    create_named_run_dir(output_root, &format_run_directory_name(timestamp))
89}