dsfb_computer_graphics/
outputs.rs1use 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}