25a0f1260f
First implementation milestone: Cargo project, dependencies,
and a minimal but functional TUI shell built on Ratatui +
Crossterm + Tokio in the Elm-style update/view pattern
(Candidate A from Phase 2/3 selection).
Includes:
- Three-region layout: items list (left), output + input + hint
(right), bottom status bar with mode-aware shortcuts.
- Two themes (light, dark) plus COLORFGBG auto-detect, per
NFR-7. CLI: --theme {light,dark}, --log-file <path>.
- Input modes per ADR-0003: simple (default), advanced, with
the `:` one-shot escape including immediate prompt reaction
("Advanced:" label, advanced border) and auto-inserted space
after a leading `:` in simple mode.
- App-level commands: `quit`/`q`, `mode simple`/`mode advanced`
(canonical list per ADR-0003 — remaining commands land in
later iterations).
- File logging via tracing, defaulting to ~/.rdbms-playground/
playground.log so the TUI is not corrupted by stdio.
Testing per ADR-0008:
- Tier 1: 29 unit tests covering input handling, mode switch,
one-shot escape, auto-space, output buffering, CLI parsing.
- Tier 2: 4 insta snapshots (default simple/advanced/light,
one-shot active) of TestBackend frames.
- Tier 3: 7 integration tests driving synthetic events through
App::update + render path.
All green: 36 tests, 0 failures, 0 skips. Clippy clean with
nursery lints enabled.
75 lines
2.6 KiB
Rust
75 lines
2.6 KiB
Rust
//! Tracing-based logging setup.
|
|
//!
|
|
//! TUI applications cannot write logs to stdout/stderr without
|
|
//! corrupting the terminal, so logs go to a file. The path comes
|
|
//! from the CLI (`--log-file`) or the `RDBMS_PLAYGROUND_LOG_FILE`
|
|
//! environment variable; if neither is set we default to
|
|
//! `~/.rdbms-playground/playground.log` and create directories as
|
|
//! needed.
|
|
|
|
use std::fs::{File, OpenOptions, create_dir_all};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{Context, Result};
|
|
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
const DEFAULT_LOG_DIR: &str = ".rdbms-playground";
|
|
const DEFAULT_LOG_FILE: &str = "playground.log";
|
|
|
|
/// Initialise tracing to write to the given file path, or to a
|
|
/// platform-default path when `path` is `None`.
|
|
pub fn init(path: Option<&Path>) -> Result<PathBuf> {
|
|
let chosen = match path {
|
|
Some(p) => p.to_path_buf(),
|
|
None => default_log_path()?,
|
|
};
|
|
if let Some(parent) = chosen.parent() {
|
|
create_dir_all(parent)
|
|
.with_context(|| format!("create log directory {}", parent.display()))?;
|
|
}
|
|
let file = open_log_file(&chosen)?;
|
|
let filter = EnvFilter::try_from_env("RDBMS_PLAYGROUND_LOG")
|
|
.unwrap_or_else(|_| EnvFilter::new("info"));
|
|
let layer = fmt::layer()
|
|
.with_writer(file)
|
|
.with_ansi(false)
|
|
.with_target(true);
|
|
tracing_subscriber::registry()
|
|
.with(filter)
|
|
.with(layer)
|
|
.init();
|
|
tracing::info!(path = %chosen.display(), "logging initialised");
|
|
Ok(chosen)
|
|
}
|
|
|
|
fn open_log_file(path: &Path) -> Result<File> {
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(path)
|
|
.with_context(|| format!("open log file {}", path.display()))
|
|
}
|
|
|
|
fn default_log_path() -> Result<PathBuf> {
|
|
let home = home_dir().context("could not determine HOME directory for default log path")?;
|
|
Ok(home.join(DEFAULT_LOG_DIR).join(DEFAULT_LOG_FILE))
|
|
}
|
|
|
|
fn home_dir() -> Option<PathBuf> {
|
|
// std::env::home_dir is deprecated; do the lookup ourselves with
|
|
// the platform-conventional environment variables. Once we add a
|
|
// proper path strategy (project storage, ADR-0004) this can be
|
|
// replaced with the chosen helper crate.
|
|
if let Some(p) = std::env::var_os("HOME") {
|
|
return Some(PathBuf::from(p));
|
|
}
|
|
if let (Some(drive), Some(path)) =
|
|
(std::env::var_os("HOMEDRIVE"), std::env::var_os("HOMEPATH"))
|
|
{
|
|
let mut combined = PathBuf::from(drive);
|
|
combined.push(path);
|
|
return Some(combined);
|
|
}
|
|
std::env::var_os("USERPROFILE").map(PathBuf::from)
|
|
}
|