calcit 0.13.42

Interpreter and js codegen for Calcit
Documentation
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

pub const PROJECT_STATE_DIRECTORY: &str = ".calcit";
pub const CURSOR_STATE_FILE: &str = "cursor.cirru";
pub const ERROR_STATE_FILE: &str = "error.cirru";

static ACTIVE_PROJECT_DIRECTORY: OnceLock<PathBuf> = OnceLock::new();

pub fn project_directory_for_snapshot(snapshot_file: &str) -> &Path {
  Path::new(snapshot_file)
    .parent()
    .filter(|path| !path.as_os_str().is_empty())
    .unwrap_or(Path::new("."))
}

pub fn state_directory(project_directory: &Path) -> PathBuf {
  project_directory.join(PROJECT_STATE_DIRECTORY)
}

pub fn state_file(project_directory: &Path, file_name: &str) -> PathBuf {
  state_directory(project_directory).join(file_name)
}

pub fn state_file_for_snapshot(snapshot_file: &str, file_name: &str) -> PathBuf {
  state_file(project_directory_for_snapshot(snapshot_file), file_name)
}

pub fn set_active_project_directory_from_snapshot(snapshot_file: &str) {
  let _ = ACTIVE_PROJECT_DIRECTORY.set(project_directory_for_snapshot(snapshot_file).to_path_buf());
}

pub fn active_state_file(file_name: &str) -> PathBuf {
  state_file(
    ACTIVE_PROJECT_DIRECTORY.get().map(PathBuf::as_path).unwrap_or(Path::new(".")),
    file_name,
  )
}

pub fn ensure_state_directory(project_directory: &Path) -> io::Result<PathBuf> {
  let directory = state_directory(project_directory);
  fs::create_dir_all(&directory)?;
  Ok(directory)
}

pub fn migrate_legacy_file(legacy: &Path, destination: &Path) -> io::Result<bool> {
  if destination.exists() || !legacy.exists() {
    return Ok(false);
  }
  if let Some(parent) = destination.parent() {
    fs::create_dir_all(parent)?;
  }
  fs::rename(legacy, destination)?;
  Ok(true)
}

#[cfg(test)]
mod tests {
  use super::{CURSOR_STATE_FILE, migrate_legacy_file, state_file_for_snapshot};
  use std::fs;

  #[test]
  fn state_file_is_next_to_snapshot_under_calcit_directory() {
    assert_eq!(
      state_file_for_snapshot("examples/demo/calcit.cirru", CURSOR_STATE_FILE),
      std::path::PathBuf::from("examples/demo/.calcit/cursor.cirru")
    );
    assert_eq!(
      state_file_for_snapshot("calcit.cirru", CURSOR_STATE_FILE),
      std::path::PathBuf::from("./.calcit/cursor.cirru")
    );
  }

  #[test]
  fn legacy_file_moves_once_without_overwriting_destination() {
    let directory = std::env::temp_dir().join(format!(
      "calcit-project-state-test-{}-{}",
      std::process::id(),
      std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("test clock should be valid")
        .as_nanos()
    ));
    fs::create_dir(&directory).expect("temp dir should create");
    let legacy = directory.join(".calcit-cursor.cirru");
    let destination = directory.join(".calcit/cursor.cirru");
    fs::write(&legacy, "legacy").expect("legacy state should write");

    assert!(migrate_legacy_file(&legacy, &destination).expect("legacy state should migrate"));
    assert_eq!(fs::read_to_string(&destination).expect("new state should read"), "legacy");
    assert!(!legacy.exists());

    fs::write(&legacy, "new legacy").expect("second legacy state should write");
    assert!(!migrate_legacy_file(&legacy, &destination).expect("existing destination should win"));
    assert_eq!(fs::read_to_string(&destination).expect("new state should remain"), "legacy");
    fs::remove_dir_all(directory).expect("temp dir should clean up");
  }
}