Wire everything up.
This commit is contained in:
parent
c564f197b5
commit
05176c7742
15 changed files with 726 additions and 49 deletions
104
src/app/workspace.rs
Normal file
104
src/app/workspace.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
//! `.skate/` runtime directory management.
|
||||
//!
|
||||
//! The `.skate/` directory lives inside the user's project and holds all
|
||||
//! runtime artefacts produced by a skate session: structured logs, session
|
||||
//! indices, and (in future) per-run snapshots. None of these should ever be
|
||||
//! committed to the project's VCS, so the first time the directory is created
|
||||
//! we drop a `.gitignore` containing `*` -- ignoring everything, including the
|
||||
//! `.gitignore` itself.
|
||||
//!
|
||||
//! # Lifecycle
|
||||
//!
|
||||
//! ```text
|
||||
//! app::run
|
||||
//! -> SkateDir::open(project_dir) -- creates dir + .gitignore if needed
|
||||
//! -> skate_dir.init_tracing() -- opens skate.log, installs subscriber
|
||||
//! ```
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Context;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
/// The `.skate/` runtime directory inside a project.
|
||||
///
|
||||
/// Created on first use; subsequent calls are no-ops. All knowledge of
|
||||
/// well-known child paths stays inside this module so callers never
|
||||
/// construct them by hand.
|
||||
pub struct SkateDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl SkateDir {
|
||||
/// Open (or create) the `.skate/` directory inside `project_dir`.
|
||||
///
|
||||
/// On first call this also writes a `.gitignore` containing `*` so that
|
||||
/// none of the runtime files are accidentally committed. Concretely:
|
||||
///
|
||||
/// 1. `create_dir_all` -- idempotent, works whether the dir already exists
|
||||
/// or is being created for the first time.
|
||||
/// 2. `OpenOptions::create_new` on `.gitignore` -- atomic write-once; the
|
||||
/// `AlreadyExists` error is silently ignored so repeated calls are safe.
|
||||
///
|
||||
/// Returns `Err` on any I/O failure other than `AlreadyExists`.
|
||||
pub fn open(project_dir: &Path) -> anyhow::Result<Self> {
|
||||
let path = project_dir.join(".skate");
|
||||
|
||||
std::fs::create_dir_all(&path)
|
||||
.with_context(|| format!("cannot create .skate directory {}", path.display()))?;
|
||||
|
||||
// Write .gitignore on first creation; no-op if it already exists.
|
||||
// Content is "*": ignore everything in this directory including the
|
||||
// .gitignore itself -- none of the skate runtime files should be committed.
|
||||
let gitignore_path = path.join(".gitignore");
|
||||
match std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&gitignore_path)
|
||||
{
|
||||
Ok(mut f) => {
|
||||
use std::io::Write;
|
||||
f.write_all(b"*\n")
|
||||
.with_context(|| format!("cannot write {}", gitignore_path.display()))?;
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||
Err(e) => {
|
||||
return Err(e)
|
||||
.with_context(|| format!("cannot create {}", gitignore_path.display()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { path })
|
||||
}
|
||||
|
||||
/// Install the global `tracing` subscriber, writing to `skate.log`.
|
||||
///
|
||||
/// Opens (or creates) `skate.log` in append mode, then registers a
|
||||
/// `tracing_subscriber::fmt` subscriber that writes structured JSON-ish
|
||||
/// text to that file. Writing to stdout is not possible because the TUI
|
||||
/// owns the terminal.
|
||||
///
|
||||
/// RUST_LOG controls verbosity; falls back to `info` if absent or
|
||||
/// unparseable. Must be called at most once per process -- the underlying
|
||||
/// `tracing` registry panics on a second `init()` call.
|
||||
pub fn init_tracing(&self) -> anyhow::Result<()> {
|
||||
let log_path = self.path.join("skate.log");
|
||||
let log_file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_path)
|
||||
.with_context(|| format!("cannot open log file {}", log_path.display()))?;
|
||||
|
||||
let filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_writer(Mutex::new(log_file))
|
||||
.with_ansi(false)
|
||||
.with_env_filter(filter)
|
||||
.init();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue