diff --git a/Cargo.lock b/Cargo.lock index 6b2da36..88a8426 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -1031,6 +1040,8 @@ dependencies = [ "serde_json", "smol", "thiserror 2.0.18", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1055,6 +1066,7 @@ dependencies = [ "smol", "thiserror 2.0.18", "toml 0.8.23", + "tracing", "walkdir", "which", ] @@ -1084,6 +1096,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "toml 0.8.23", + "tracing", "walkdir", ] @@ -2016,6 +2029,15 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.7.6" @@ -2087,6 +2109,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2693,12 +2724,29 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-lite" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + [[package]] name = "rust-embed" version = "8.11.0" @@ -3004,6 +3052,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3338,6 +3395,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.2.27" @@ -3467,6 +3533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -3479,6 +3546,48 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + [[package]] name = "type-map" version = "0.5.1" @@ -3599,6 +3708,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.12.0" diff --git a/Cargo.toml b/Cargo.toml index 47fc4a0..3679152 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,5 @@ ratatui = "0.29" crossterm = "0.28" thiserror = "2" anyhow = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] } diff --git a/crates/doot-cli/Cargo.toml b/crates/doot-cli/Cargo.toml index d82eeef..ed5b5a2 100644 --- a/crates/doot-cli/Cargo.toml +++ b/crates/doot-cli/Cargo.toml @@ -21,3 +21,5 @@ thiserror.workspace = true anyhow.workspace = true dirs.workspace = true blake3.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true diff --git a/crates/doot-cli/src/commands/apply.rs b/crates/doot-cli/src/commands/apply.rs index 6746c8d..81d1460 100644 --- a/crates/doot-cli/src/commands/apply.rs +++ b/crates/doot-cli/src/commands/apply.rs @@ -9,18 +9,12 @@ use std::io::{self, Write}; use std::path::PathBuf; use std::process::Command; -pub fn run( - config_path: Option, - dry_run: bool, - parallel: bool, - verbose: bool, -) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(dry_run, parallel))] +pub fn run(config_path: Option, dry_run: bool, parallel: bool) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; - if verbose { - println!("parsing {}", path.display()); - } + tracing::debug!(path = %path.display(), "parsing config"); let program = parse_config(&path)?; type_check(&program, &source, &path.display().to_string())?; @@ -45,18 +39,18 @@ pub fn run( // Handle errors if !validation.errors.is_empty() { - eprintln!("\nDotfile configuration errors:"); + tracing::error!("dotfile configuration errors detected"); for error in &validation.errors { match error { DotfileConflict::Duplicate { index_a, index_b } => { let a = &result.dotfiles[*index_a]; let b = &result.dotfiles[*index_b]; - eprintln!( - " [error] duplicate entry: '{}' -> '{}' appears twice (entries {} and {})", - a.source.display(), - a.target.display(), - index_a + 1, - index_b + 1 + tracing::error!( + source = %a.source.display(), + target = %a.target.display(), + index_a = index_a + 1, + index_b = index_b + 1, + "duplicate entry" ); let _ = b; // silence unused warning } @@ -66,12 +60,12 @@ pub fn run( } => { let parent = &result.dotfiles[*parent_index]; let child = &result.dotfiles[*child_index]; - eprintln!( - " [error] redundant overlap: '{}' already includes '{}' (entries {} and {})", - parent.source.display(), - child.source.display(), - parent_index + 1, - child_index + 1 + tracing::error!( + parent_source = %parent.source.display(), + child_source = %child.source.display(), + parent_index = parent_index + 1, + child_index = child_index + 1, + "redundant overlap" ); let _ = child; // silence unused warning } @@ -82,11 +76,9 @@ pub fn run( // Show warnings if !validation.warnings.is_empty() { - eprintln!("\nDotfile configuration warnings:"); for warning in &validation.warnings { - eprintln!(" [warn] {}", warning.message); + tracing::warn!(message = %warning.message, "dotfile configuration warning"); } - eprintln!(); } // Reorder dotfiles based on dependency analysis @@ -98,7 +90,6 @@ pub fn run( let config = Config::new(source_dir.clone()) .dry_run(dry_run) - .verbose(verbose) .parallel(parallel); let state_file = config.state_file.clone(); @@ -133,16 +124,12 @@ pub fn run( | SyncStatus::SourceChanged => { // Can auto-merge: just copy from source has_changes = true; - if verbose { - println!(" [source changed] {}", src.display()); - } + tracing::debug!(source = %src.display(), "source changed"); } SyncStatus::TargetChanged => { // Target changed but source didn't - keep target, will update state has_changes = true; - if verbose { - println!(" [target changed, keeping] {}", tgt.display()); - } + tracing::debug!(target = %tgt.display(), "target changed, keeping"); } SyncStatus::Conflict => { // Real conflict - both sides changed this file @@ -151,9 +138,7 @@ pub fn run( } SyncStatus::SourceMissing => { has_changes = true; - if verbose { - println!(" [removed from source] {}", tgt.display()); - } + tracing::debug!(target = %tgt.display(), "removed from source"); } } } @@ -162,16 +147,14 @@ pub fn run( conflicts.push((dotfile, SyncStatus::Conflict)); } else if has_changes { to_deploy.push(dotfile); - } else if verbose { - println!(" [synced] {}", dotfile.target.display()); + } else { + tracing::debug!(target = %dotfile.target.display(), "synced"); } } else { // Single file handling (unchanged) match status { SyncStatus::Synced => { - if verbose { - println!(" [synced] {}", dotfile.target.display()); - } + tracing::debug!(target = %dotfile.target.display(), "synced"); } SyncStatus::NotDeployed | SyncStatus::TargetMissing => { to_deploy.push(dotfile); @@ -191,7 +174,7 @@ pub fn run( conflicts.push((dotfile, status)); } SyncStatus::SourceMissing => { - eprintln!(" [error] source missing: {}", dotfile.source.display()); + tracing::error!(source = %dotfile.source.display(), "source missing"); } } } @@ -358,7 +341,7 @@ pub fn run( } // Run before_deploy hooks - run_hooks(&result.hooks, HookStage::BeforeDeploy, verbose, &hook_env)?; + run_hooks(&result.hooks, HookStage::BeforeDeploy, &hook_env)?; if to_deploy.is_empty() { println!("\nNothing to deploy (all files synced)."); @@ -386,13 +369,11 @@ pub fn run( println!(" errors: {}", deploy_result.errors.len()); for deployed in &deploy_result.deployed { - if verbose { - println!( - " [ok] {} -> {}", - deployed.source.display(), - deployed.target.display() - ); - } + tracing::debug!( + source = %deployed.source.display(), + target = %deployed.target.display(), + "deployed" + ); } for skipped in &deploy_result.skipped { @@ -400,21 +381,21 @@ pub fn run( } for error in &deploy_result.errors { - eprintln!( - " [err] {} -> {}: {}", - error.source.display(), - error.target.display(), - error.error + tracing::error!( + source = %error.source.display(), + target = %error.target.display(), + error = %error.error, + "deployment failed" ); } } // Run after_deploy hooks - run_hooks(&result.hooks, HookStage::AfterDeploy, verbose, &hook_env)?; + run_hooks(&result.hooks, HookStage::AfterDeploy, &hook_env)?; if !result.packages.is_empty() { // Run before_package hooks - run_hooks(&result.hooks, HookStage::BeforePackage, verbose, &hook_env)?; + run_hooks(&result.hooks, HookStage::BeforePackage, &hook_env)?; if let Some(manager) = doot_core::package::detect_package_manager() { // Filter out already installed packages @@ -430,10 +411,13 @@ pub fn run( } } - if !already_installed.is_empty() && verbose { - println!("\npackages already installed:"); + if !already_installed.is_empty() { + tracing::debug!( + count = already_installed.len(), + "packages already installed" + ); for pkg in &already_installed { - println!(" [ok] {}", pkg); + tracing::debug!(package = %pkg, "already installed"); } } @@ -460,12 +444,13 @@ pub fn run( } // Run after_package hooks - run_hooks(&result.hooks, HookStage::AfterPackage, verbose, &hook_env)?; + run_hooks(&result.hooks, HookStage::AfterPackage, &hook_env)?; } Ok(()) } +#[tracing::instrument(skip_all)] fn show_diff(source: &PathBuf, target: &PathBuf) { use std::process::Command; @@ -485,10 +470,10 @@ fn show_diff(source: &PathBuf, target: &PathBuf) { } } +#[tracing::instrument(skip_all)] fn run_hooks( hooks: &[HookConfig], stage: HookStage, - verbose: bool, env_vars: &std::collections::HashMap, ) -> anyhow::Result<()> { let stage_hooks: Vec<_> = hooks.iter().filter(|h| h.stage == stage).collect(); @@ -504,14 +489,10 @@ fn run_hooks( HookStage::AfterPackage => "after_package", }; - if verbose { - println!("\nrunning {} hooks...", stage_name); - } + tracing::debug!(stage = stage_name, "running hooks"); for hook in stage_hooks { - if verbose { - println!(" $ {}", hook.run); - } + tracing::debug!(command = %hook.run, "executing hook"); let status = Command::new("sh") .arg("-c") @@ -527,6 +508,7 @@ fn run_hooks( Ok(()) } +#[tracing::instrument(skip_all)] fn merge_in_editor(source: &PathBuf, target: &PathBuf) -> anyhow::Result { use std::process::Command; diff --git a/crates/doot-cli/src/commands/check.rs b/crates/doot-cli/src/commands/check.rs index f95a75e..a7f1e11 100644 --- a/crates/doot-cli/src/commands/check.rs +++ b/crates/doot-cli/src/commands/check.rs @@ -1,13 +1,12 @@ use super::{find_config_file, parse_config, type_check}; use std::path::PathBuf; -pub fn run(config_path: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn run(config_path: Option) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; - if verbose { - println!("checking {}", path.display()); - } + tracing::debug!(path = %path.display(), "checking config"); let program = parse_config(&path)?; println!("syntax: ok"); diff --git a/crates/doot-cli/src/commands/decrypt.rs b/crates/doot-cli/src/commands/decrypt.rs index dcfdfe3..0250701 100644 --- a/crates/doot-cli/src/commands/decrypt.rs +++ b/crates/doot-cli/src/commands/decrypt.rs @@ -1,7 +1,8 @@ use doot_core::{Config, encryption::AgeEncryption}; use std::path::PathBuf; -pub fn run(file: PathBuf, identity: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(file = %file.display()))] +pub fn run(file: PathBuf, identity: Option) -> anyhow::Result<()> { let config = Config::default(); let identity_key = if let Some(path) = identity { std::fs::read_to_string(&path)?.trim().to_string() @@ -18,9 +19,7 @@ pub fn run(file: PathBuf, identity: Option, verbose: bool) -> anyhow::R ); }; - if verbose { - println!("decrypting {}", file.display()); - } + tracing::debug!(file = %file.display(), "decrypting file"); let encryption = AgeEncryption::new().with_identity(&identity_key)?; diff --git a/crates/doot-cli/src/commands/diff.rs b/crates/doot-cli/src/commands/diff.rs index fa2ea61..b41f0af 100644 --- a/crates/doot-cli/src/commands/diff.rs +++ b/crates/doot-cli/src/commands/diff.rs @@ -3,7 +3,8 @@ use doot_core::deploy::DiffDisplay; use doot_lang::Evaluator; use std::path::PathBuf; -pub fn run(config_path: Option, all: bool, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(all))] +pub fn run(config_path: Option, all: bool) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -22,9 +23,7 @@ pub fn run(config_path: Option, all: bool, verbose: bool) -> anyhow::Re let target_path = &dotfile.target; if !source_path.exists() { - if verbose { - println!("[missing] {} (source not found)", source_path.display()); - } + tracing::debug!(source = %source_path.display(), "source not found, skipping"); continue; } diff --git a/crates/doot-cli/src/commands/edit.rs b/crates/doot-cli/src/commands/edit.rs index 9489d2f..03cc33d 100644 --- a/crates/doot-cli/src/commands/edit.rs +++ b/crates/doot-cli/src/commands/edit.rs @@ -9,12 +9,12 @@ use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::Command; +#[tracing::instrument(skip_all, fields(target = %target, auto_apply, skip_prompt))] pub fn run( config_path: Option, target: String, auto_apply: bool, skip_prompt: bool, - verbose: bool, ) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -33,9 +33,7 @@ pub fn run( let (source_file, dotfile) = find_source_and_dotfile(&target_path, &result.dotfiles, &source_dir, &state)?; - if verbose { - println!("editing source: {}", source_file.display()); - } + tracing::debug!(source = %source_file.display(), "editing source file"); // Get hash before editing let hash_before = hash_file(&source_file); @@ -67,7 +65,7 @@ pub fn run( if should_apply { if let Some(df) = dotfile { - apply_single(&source_file, &df.target, df, &config, verbose)?; + apply_single(&source_file, &df.target, df, &config)?; println!("applied changes to {}", df.target.display()); } else { println!("hint: run 'doot apply' to deploy changes"); @@ -95,12 +93,12 @@ fn hash_file(path: &PathBuf) -> String { .unwrap_or_default() } +#[tracing::instrument(skip_all, fields(source = %source.display(), target = %target.display()))] fn apply_single( source: &PathBuf, target: &PathBuf, dotfile: &doot_lang::evaluator::DotfileConfig, config: &Config, - verbose: bool, ) -> anyhow::Result<()> { let deploy_mode = match dotfile.deploy { doot_lang::evaluator::DeployMode::Copy => DeployMode::Copy, @@ -122,9 +120,11 @@ fn apply_single( .map_err(|e| anyhow::anyhow!("template error: {}", e))?; std::fs::write(target, rendered)?; - if verbose { - println!("rendered {} -> {}", source.display(), target.display()); - } + tracing::debug!( + source = %source.display(), + target = %target.display(), + "rendered template" + ); state.record_deployment_with_template(source, target, DeployMode::Copy, true); state.save()?; @@ -135,9 +135,11 @@ fn apply_single( DeployMode::Link => { let linker = Linker::new(config.clone()); linker.link(source, target)?; - if verbose { - println!("linked {} -> {}", source.display(), target.display()); - } + tracing::debug!( + source = %source.display(), + target = %target.display(), + "linked" + ); } DeployMode::Copy => { if let Some(parent) = target.parent() { @@ -148,9 +150,11 @@ fn apply_single( } else { std::fs::copy(source, target)?; } - if verbose { - println!("copied {} -> {}", source.display(), target.display()); - } + tracing::debug!( + source = %source.display(), + target = %target.display(), + "copied" + ); } } @@ -177,14 +181,17 @@ fn copy_dir_recursive(src: &PathBuf, dst: &PathBuf) -> std::io::Result<()> { Ok(()) } +#[tracing::instrument(level = "trace")] fn expand_tilde(path: &str) -> PathBuf { if path.starts_with("~/") - && let Some(home) = dirs::home_dir() { - return home.join(&path[2..]); - } + && let Some(home) = dirs::home_dir() + { + return home.join(&path[2..]); + } PathBuf::from(path) } +#[tracing::instrument(skip_all)] fn find_source_and_dotfile<'a>( target: &PathBuf, dotfiles: &'a [doot_lang::evaluator::DotfileConfig], diff --git a/crates/doot-cli/src/commands/encrypt.rs b/crates/doot-cli/src/commands/encrypt.rs index 9673794..3c0e0f2 100644 --- a/crates/doot-cli/src/commands/encrypt.rs +++ b/crates/doot-cli/src/commands/encrypt.rs @@ -1,7 +1,8 @@ use doot_core::{Config, encryption::AgeEncryption}; use std::path::PathBuf; -pub fn run(file: PathBuf, recipient: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(file = %file.display()))] +pub fn run(file: PathBuf, recipient: Option) -> anyhow::Result<()> { let config_dir = Config::default_config_dir(); let recipient_key = if let Some(r) = recipient { r @@ -19,13 +20,11 @@ pub fn run(file: PathBuf, recipient: Option, verbose: bool) -> anyhow::R } }; - if verbose { - println!( - "encrypting {} with recipient {}", - file.display(), - &recipient_key[..20] - ); - } + tracing::debug!( + file = %file.display(), + recipient_prefix = &recipient_key[..20.min(recipient_key.len())], + "encrypting file" + ); let mut encryption = AgeEncryption::new(); encryption.add_recipient(&recipient_key)?; diff --git a/crates/doot-cli/src/commands/fmt.rs b/crates/doot-cli/src/commands/fmt.rs index eed0d85..9fc0dec 100644 --- a/crates/doot-cli/src/commands/fmt.rs +++ b/crates/doot-cli/src/commands/fmt.rs @@ -1,7 +1,8 @@ use super::find_config_file; use std::path::PathBuf; -pub fn run(config_path: Option, check: bool, _verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(check))] +pub fn run(config_path: Option, check: bool) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -24,6 +25,7 @@ pub fn run(config_path: Option, check: bool, _verbose: bool) -> anyhow: Ok(()) } +#[tracing::instrument(level = "trace", skip_all)] fn format_source(source: &str) -> String { let mut result = String::new(); let mut indent_level = 0; diff --git a/crates/doot-cli/src/commands/init.rs b/crates/doot-cli/src/commands/init.rs index e04876c..5fe62c0 100644 --- a/crates/doot-cli/src/commands/init.rs +++ b/crates/doot-cli/src/commands/init.rs @@ -1,17 +1,19 @@ use doot_core::Config; use std::path::{Path, PathBuf}; -pub fn run(path: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn run(path: Option) -> anyhow::Result<()> { let source_dir = path.unwrap_or_else(Config::default_source_dir); let config = Config::new(source_dir.clone()); let is_default = source_dir == Config::default_config_dir(); - if verbose { - println!("config dir: {}", config.config_dir.display()); - println!("state dir: {}", config.state_dir.display()); - if !is_default { - println!("source dir: {}", source_dir.display()); - } + tracing::debug!( + config_dir = %config.config_dir.display(), + state_dir = %config.state_dir.display(), + "initializing doot" + ); + if !is_default { + tracing::debug!(source_dir = %source_dir.display(), "custom source directory"); } config.ensure_dirs()?; @@ -60,6 +62,7 @@ pub fn run(path: Option, verbose: bool) -> anyhow::Result<()> { Ok(()) } +#[tracing::instrument(skip_all)] fn example_config_with_source(source_dir: &Path) -> String { format!( r#"# doot.doot diff --git a/crates/doot-cli/src/commands/lsp.rs b/crates/doot-cli/src/commands/lsp.rs index 1a4cb30..60fc89b 100644 --- a/crates/doot-cli/src/commands/lsp.rs +++ b/crates/doot-cli/src/commands/lsp.rs @@ -1,3 +1,4 @@ +#[tracing::instrument] pub fn run() -> anyhow::Result<()> { println!("doot language server"); println!("LSP support is not yet implemented"); diff --git a/crates/doot-cli/src/commands/mod.rs b/crates/doot-cli/src/commands/mod.rs index abd1a99..d3b1b55 100644 --- a/crates/doot-cli/src/commands/mod.rs +++ b/crates/doot-cli/src/commands/mod.rs @@ -17,6 +17,7 @@ use doot_core::Config; use doot_lang::{Lexer, Parser, TypeChecker}; use std::path::PathBuf; +#[tracing::instrument(skip_all)] pub fn find_config_file(base: Option) -> anyhow::Result { if let Some(path) = base { if path.exists() { @@ -47,6 +48,7 @@ fn byte_offset_to_line(source: &str, offset: usize) -> usize { + 1 } +#[tracing::instrument(skip_all, fields(path = %path.display()))] pub fn parse_config(path: &PathBuf) -> anyhow::Result { let source = std::fs::read_to_string(path)?; let tokens = Lexer::lex(&source).map_err(|errs| { @@ -76,6 +78,7 @@ pub fn parse_config(path: &PathBuf) -> anyhow::Result { Ok(program) } +#[tracing::instrument(skip_all)] pub fn type_check( program: &doot_lang::Program, source: &str, diff --git a/crates/doot-cli/src/commands/package.rs b/crates/doot-cli/src/commands/package.rs index 2764747..cd2fd92 100644 --- a/crates/doot-cli/src/commands/package.rs +++ b/crates/doot-cli/src/commands/package.rs @@ -2,7 +2,8 @@ use super::{find_config_file, parse_config, type_check}; use doot_lang::Evaluator; use std::path::PathBuf; -pub fn install(config_path: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn install(config_path: Option) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -20,9 +21,7 @@ pub fn install(config_path: Option, verbose: bool) -> anyhow::Result<() let manager = doot_core::package::detect_package_manager() .ok_or_else(|| anyhow::anyhow!("no supported package manager found"))?; - if verbose { - println!("using package manager: {}", manager.name()); - } + tracing::debug!(manager = %manager.name(), "using package manager"); let package_names: Vec = result .packages @@ -43,9 +42,7 @@ pub fn install(config_path: Option, verbose: bool) -> anyhow::Result<() println!("installing {} packages...", package_names.len()); for name in &package_names { - if verbose { - println!(" {}", name); - } + tracing::debug!(package = %name, "queued for install"); } manager.install(&package_names)?; @@ -54,13 +51,12 @@ pub fn install(config_path: Option, verbose: bool) -> anyhow::Result<() Ok(()) } -pub fn update(verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn update() -> anyhow::Result<()> { let manager = doot_core::package::detect_package_manager() .ok_or_else(|| anyhow::anyhow!("no supported package manager found"))?; - if verbose { - println!("updating package index with {}", manager.name()); - } + tracing::debug!(manager = %manager.name(), "updating package index"); manager.update()?; println!("package index updated"); @@ -68,7 +64,8 @@ pub fn update(verbose: bool) -> anyhow::Result<()> { Ok(()) } -pub fn list(config_path: Option, _verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn list(config_path: Option) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; diff --git a/crates/doot-cli/src/commands/rollback.rs b/crates/doot-cli/src/commands/rollback.rs index 4565cbc..b438e66 100644 --- a/crates/doot-cli/src/commands/rollback.rs +++ b/crates/doot-cli/src/commands/rollback.rs @@ -4,11 +4,8 @@ use doot_core::{ }; use std::path::PathBuf; -pub fn run( - _config_path: Option, - snapshot_name: Option, - verbose: bool, -) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn run(_config_path: Option, snapshot_name: Option) -> anyhow::Result<()> { let config = Config::default(); let name = if let Some(n) = snapshot_name { @@ -33,10 +30,11 @@ pub fn run( anyhow::bail!("please specify a snapshot name or 'last'"); }; - if verbose { - println!("rolling back to snapshot: {}", name); - println!(" snapshot dir: {}", config.snapshot_dir.display()); - } + tracing::debug!( + snapshot = %name, + snapshot_dir = %config.snapshot_dir.display(), + "rolling back to snapshot" + ); let snapshot = Snapshot::load(&name, &config.snapshot_dir)?; @@ -44,32 +42,26 @@ pub fn run( let target = PathBuf::from(target_str); if target.is_symlink() { - if verbose { - println!("removing symlink: {}", target.display()); - } + tracing::debug!(target = %target.display(), "removing symlink"); std::fs::remove_file(&target)?; } match record.mode { DeployMode::Link => { - if verbose { - println!( - "recreating symlink: {} -> {}", - record.source.display(), - target.display() - ); - } + tracing::debug!( + source = %record.source.display(), + target = %target.display(), + "recreating symlink" + ); #[cfg(unix)] std::os::unix::fs::symlink(&record.source, &target)?; } DeployMode::Copy => { - if verbose { - println!( - "restoring copy: {} -> {}", - record.source.display(), - target.display() - ); - } + tracing::debug!( + source = %record.source.display(), + target = %target.display(), + "restoring copy" + ); if record.source.exists() { std::fs::copy(&record.source, &target)?; } diff --git a/crates/doot-cli/src/commands/snapshot.rs b/crates/doot-cli/src/commands/snapshot.rs index 149e3ba..4a0049c 100644 --- a/crates/doot-cli/src/commands/snapshot.rs +++ b/crates/doot-cli/src/commands/snapshot.rs @@ -4,17 +4,18 @@ use doot_core::{ }; use std::path::PathBuf; -pub fn run(_config_path: Option, name: String, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all, fields(name = %name))] +pub fn run(_config_path: Option, name: String) -> anyhow::Result<()> { let config = Config::default(); config.ensure_dirs()?; let mut state = StateStore::new(&config.state_file); - if verbose { - println!("creating snapshot: {}", name); - println!(" state file: {}", config.state_file.display()); - println!(" snapshot dir: {}", config.snapshot_dir.display()); - } + tracing::debug!( + state_file = %config.state_file.display(), + snapshot_dir = %config.snapshot_dir.display(), + "creating snapshot" + ); let state_content = std::fs::read_to_string(&config.state_file).unwrap_or_else(|_| "{}".to_string()); diff --git a/crates/doot-cli/src/commands/status.rs b/crates/doot-cli/src/commands/status.rs index 9d23d66..07e3673 100644 --- a/crates/doot-cli/src/commands/status.rs +++ b/crates/doot-cli/src/commands/status.rs @@ -3,7 +3,8 @@ use doot_core::state::StateStore; use doot_lang::Evaluator; use std::path::PathBuf; -pub fn run(config_path: Option, verbose: bool) -> anyhow::Result<()> { +#[tracing::instrument(skip_all)] +pub fn run(config_path: Option) -> anyhow::Result<()> { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -57,8 +58,12 @@ pub fn run(config_path: Option, verbose: bool) -> anyhow::Result<()> { target.display() ); - if verbose && status != "ok" && status != "deployed" { - println!(" status: {}", status); + if status != "ok" && status != "deployed" { + tracing::debug!( + target = %target.display(), + status = status, + "dotfile status detail" + ); } } diff --git a/crates/doot-cli/src/commands/tui.rs b/crates/doot-cli/src/commands/tui.rs index df36034..623ef42 100644 --- a/crates/doot-cli/src/commands/tui.rs +++ b/crates/doot-cli/src/commands/tui.rs @@ -19,6 +19,7 @@ use ratatui::{ use std::io; use std::path::PathBuf; +#[tracing::instrument(skip_all)] pub fn run(config_path: Option) -> anyhow::Result<()> { enable_raw_mode()?; let mut stdout = io::stdout(); @@ -108,6 +109,7 @@ enum FileStatus { } impl App { + #[tracing::instrument(skip_all)] fn new(config_path: Option) -> anyhow::Result { let path = find_config_file(config_path)?; let source = std::fs::read_to_string(&path)?; @@ -296,15 +298,17 @@ impl App { Tab::Dotfiles => { if let Some(i) = self.dotfile_state.selected() && let Some(item) = self.dotfiles.get_mut(i) - && item.status != FileStatus::Error { - item.selected = !item.selected; - } + && item.status != FileStatus::Error + { + item.selected = !item.selected; + } } Tab::Packages => { if let Some(i) = self.package_state.selected() - && let Some(item) = self.packages.get_mut(i) { - item.selected = !item.selected; - } + && let Some(item) = self.packages.get_mut(i) + { + item.selected = !item.selected; + } } _ => {} } @@ -352,6 +356,7 @@ impl App { } } + #[tracing::instrument(skip(self))] fn apply(&mut self) { if self.apply_state == ApplyState::Applying { return; @@ -404,14 +409,14 @@ impl App { let has_packages = self.packages.iter().any(|p| p.selected && !p.installed); let has_owner = self.dotfiles.iter().any(|d| d.selected); - if has_packages - && let Some(manager) = doot_core::package::detect_package_manager() { - return manager.needs_sudo(); - } + if has_packages && let Some(manager) = doot_core::package::detect_package_manager() { + return manager.needs_sudo(); + } has_owner } + #[tracing::instrument(skip(self))] fn apply_with_sudo(&mut self) { let selected_dotfiles: Vec<_> = self .dotfiles @@ -541,6 +546,7 @@ impl App { } } +#[tracing::instrument(skip_all)] fn run_app( terminal: &mut Terminal>, config_path: Option, @@ -551,69 +557,68 @@ fn run_app( terminal.draw(|f| ui(f, &mut app))?; if let Event::Key(key) = event::read()? - && key.kind == KeyEventKind::Press { - match app.input_mode { - InputMode::Password => match key.code { - KeyCode::Enter => { - app.sudo_password = Some(app.password_input.clone()); - app.password_input.clear(); - app.input_mode = InputMode::Normal; - app.apply_state = ApplyState::Applying; - app.apply_with_sudo(); + && key.kind == KeyEventKind::Press + { + match app.input_mode { + InputMode::Password => match key.code { + KeyCode::Enter => { + app.sudo_password = Some(app.password_input.clone()); + app.password_input.clear(); + app.input_mode = InputMode::Normal; + app.apply_state = ApplyState::Applying; + app.apply_with_sudo(); + } + KeyCode::Esc => { + app.password_input.clear(); + app.input_mode = InputMode::Normal; + app.apply_state = ApplyState::Idle; + } + KeyCode::Backspace => { + app.password_input.pop(); + } + KeyCode::Char(c) => { + app.password_input.push(c); + } + _ => {} + }, + InputMode::Normal => match app.apply_state { + ApplyState::Idle => match key.code { + KeyCode::Char('q') => return Ok(()), + KeyCode::Tab => app.next_tab(), + KeyCode::BackTab => app.prev_tab(), + KeyCode::Down | KeyCode::Char('j') => app.next_item(), + KeyCode::Up | KeyCode::Char('k') => app.prev_item(), + KeyCode::Char(' ') => app.toggle_selected(), + KeyCode::Char('a') => app.select_all(), + KeyCode::Char('n') => app.select_none(), + KeyCode::Enter => app.apply(), + KeyCode::Char('1') => app.tab = Tab::Dotfiles, + KeyCode::Char('2') => app.tab = Tab::Packages, + KeyCode::Char('3') => app.tab = Tab::Secrets, + KeyCode::Char('4') => app.tab = Tab::Status, + _ => {} + }, + ApplyState::Applying => { + // Can't do anything while applying + } + ApplyState::NeedsSudo => match key.code { + KeyCode::Char('y') | KeyCode::Enter => { + app.input_mode = InputMode::Password; } - KeyCode::Esc => { - app.password_input.clear(); - app.input_mode = InputMode::Normal; + KeyCode::Char('n') | KeyCode::Esc => { app.apply_state = ApplyState::Idle; } - KeyCode::Backspace => { - app.password_input.pop(); - } - KeyCode::Char(c) => { - app.password_input.push(c); - } _ => {} }, - InputMode::Normal => match app.apply_state { - ApplyState::Idle => match key.code { - KeyCode::Char('q') => return Ok(()), - KeyCode::Tab => app.next_tab(), - KeyCode::BackTab => app.prev_tab(), - KeyCode::Down | KeyCode::Char('j') => app.next_item(), - KeyCode::Up | KeyCode::Char('k') => app.prev_item(), - KeyCode::Char(' ') => app.toggle_selected(), - KeyCode::Char('a') => app.select_all(), - KeyCode::Char('n') => app.select_none(), - KeyCode::Enter => app.apply(), - KeyCode::Char('1') => app.tab = Tab::Dotfiles, - KeyCode::Char('2') => app.tab = Tab::Packages, - KeyCode::Char('3') => app.tab = Tab::Secrets, - KeyCode::Char('4') => app.tab = Tab::Status, - _ => {} - }, - ApplyState::Applying => { - // Can't do anything while applying - } - ApplyState::NeedsSudo => match key.code { - KeyCode::Char('y') | KeyCode::Enter => { - app.input_mode = InputMode::Password; - } - KeyCode::Char('n') | KeyCode::Esc => { - app.apply_state = ApplyState::Idle; - } - _ => {} - }, - ApplyState::Done => match key.code { - KeyCode::Enter | KeyCode::Esc | KeyCode::Char('q') => { - app.dismiss_apply() - } - KeyCode::Up | KeyCode::Char('k') => app.scroll_log_up(), - KeyCode::Down | KeyCode::Char('j') => app.scroll_log_down(), - _ => {} - }, + ApplyState::Done => match key.code { + KeyCode::Enter | KeyCode::Esc | KeyCode::Char('q') => app.dismiss_apply(), + KeyCode::Up | KeyCode::Char('k') => app.scroll_log_up(), + KeyCode::Down | KeyCode::Char('j') => app.scroll_log_down(), + _ => {} }, - } + }, } + } } } diff --git a/crates/doot-cli/src/main.rs b/crates/doot-cli/src/main.rs index 6a3e8f8..30117ed 100644 --- a/crates/doot-cli/src/main.rs +++ b/crates/doot-cli/src/main.rs @@ -1,7 +1,8 @@ mod commands; -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; use std::path::PathBuf; +use tracing_subscriber::EnvFilter; #[derive(Parser)] #[command(name = "doot")] @@ -10,11 +11,39 @@ struct Cli { #[command(subcommand)] command: Commands, + /// Increase verbosity (-v = info, -vv = debug, -vvv = trace) + #[arg(short, long, global = true, action = clap::ArgAction::Count)] + verbose: u8, + + /// Suppress all log output #[arg(short, long, global = true)] - verbose: bool, + quiet: bool, #[arg(short = 'C', long, global = true)] config: Option, + + /// Log level (overrides -v/-q flags) + #[arg(long, global = true)] + log_level: Option, + + /// Log output format + #[arg(long, global = true, default_value = "text")] + log_format: LogFormatArg, +} + +#[derive(Clone, ValueEnum)] +enum LogLevelArg { + Trace, + Debug, + Info, + Warn, + Error, +} + +#[derive(Clone, ValueEnum)] +enum LogFormatArg { + Text, + Json, } #[derive(Subcommand)] @@ -102,32 +131,82 @@ enum PackageAction { fn main() -> anyhow::Result<()> { let cli = Cli::parse(); + // Priority: DOOT_LOG env > --log-level flag > -q/v flags > default (warn) + let default_level = if let Some(ref level) = cli.log_level { + match level { + LogLevelArg::Trace => "trace", + LogLevelArg::Debug => "debug", + LogLevelArg::Info => "info", + LogLevelArg::Warn => "warn", + LogLevelArg::Error => "error", + } + } else if cli.quiet { + "off" + } else { + match cli.verbose { + 0 => "warn", + 1 => "info", + 2 => "debug", + _ => "trace", + } + }; + + // Scope doot crates to chosen level, third-party at warn + // -vvvv+ enables trace for ALL crates including deps + let default_directive = if default_level == "off" { + "off".to_string() + } else if cli.verbose >= 4 { + default_level.to_string() + } else { + format!( + "doot_cli={l},doot_core={l},doot_lang={l},warn", + l = default_level + ) + }; + + let env_filter = + EnvFilter::try_from_env("DOOT_LOG").unwrap_or_else(|_| EnvFilter::new(&default_directive)); + + match cli.log_format { + LogFormatArg::Json => { + tracing_subscriber::fmt() + .json() + .with_env_filter(env_filter) + .with_target(true) + .with_writer(std::io::stderr) + .init(); + } + LogFormatArg::Text => { + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_target(true) + .with_writer(std::io::stderr) + .init(); + } + } + match cli.command { - Commands::Init { path } => commands::init::run(path, cli.verbose), + Commands::Init { path } => commands::init::run(path), Commands::Apply { dry_run, parallel } => { - commands::apply::run(cli.config, dry_run, parallel, cli.verbose) + commands::apply::run(cli.config, dry_run, parallel) } - Commands::Diff { all } => commands::diff::run(cli.config, all, cli.verbose), - Commands::Status => commands::status::run(cli.config, cli.verbose), - Commands::Check => commands::check::run(cli.config, cli.verbose), - Commands::Fmt { check } => commands::fmt::run(cli.config, check, cli.verbose), - Commands::Rollback { snapshot } => { - commands::rollback::run(cli.config, snapshot, cli.verbose) - } - Commands::Snapshot { name } => commands::snapshot::run(cli.config, name, cli.verbose), - Commands::Encrypt { file, recipient } => { - commands::encrypt::run(file, recipient, cli.verbose) - } - Commands::Decrypt { file, identity } => commands::decrypt::run(file, identity, cli.verbose), + Commands::Diff { all } => commands::diff::run(cli.config, all), + Commands::Status => commands::status::run(cli.config), + Commands::Check => commands::check::run(cli.config), + Commands::Fmt { check } => commands::fmt::run(cli.config, check), + Commands::Rollback { snapshot } => commands::rollback::run(cli.config, snapshot), + Commands::Snapshot { name } => commands::snapshot::run(cli.config, name), + Commands::Encrypt { file, recipient } => commands::encrypt::run(file, recipient), + Commands::Decrypt { file, identity } => commands::decrypt::run(file, identity), Commands::Package { action } => match action { - PackageAction::Install => commands::package::install(cli.config, cli.verbose), - PackageAction::Update => commands::package::update(cli.verbose), - PackageAction::List => commands::package::list(cli.config, cli.verbose), + PackageAction::Install => commands::package::install(cli.config), + PackageAction::Update => commands::package::update(), + PackageAction::List => commands::package::list(cli.config), }, Commands::Lsp => commands::lsp::run(), Commands::Tui => commands::tui::run(cli.config), Commands::Edit { target, apply, yes } => { - commands::edit::run(cli.config, target, apply, yes, cli.verbose) + commands::edit::run(cli.config, target, apply, yes) } } } diff --git a/crates/doot-core/Cargo.toml b/crates/doot-core/Cargo.toml index b12daed..bf0e230 100644 --- a/crates/doot-core/Cargo.toml +++ b/crates/doot-core/Cargo.toml @@ -24,3 +24,4 @@ regex-lite = "0.1" glob = "0.3" minijinja = { version = "2", features = ["builtins"] } which = "7" +tracing.workspace = true diff --git a/crates/doot-core/src/config.rs b/crates/doot-core/src/config.rs index da8bc2b..515cd6d 100644 --- a/crates/doot-core/src/config.rs +++ b/crates/doot-core/src/config.rs @@ -30,6 +30,7 @@ pub struct Config { impl Config { /// Creates a new config with the given source directory. + #[tracing::instrument(skip_all, fields(source_dir = %source_dir.display()))] pub fn new(source_dir: PathBuf) -> Self { let config_dir = Self::default_config_dir(); let state_dir = Self::default_state_dir(); @@ -106,6 +107,7 @@ impl Config { } /// Creates all required directories. + #[tracing::instrument(skip(self))] pub fn ensure_dirs(&self) -> std::io::Result<()> { std::fs::create_dir_all(&self.config_dir)?; std::fs::create_dir_all(&self.state_dir)?; diff --git a/crates/doot-core/src/deploy/diff.rs b/crates/doot-core/src/deploy/diff.rs index ae39bc3..8c1ad65 100644 --- a/crates/doot-core/src/deploy/diff.rs +++ b/crates/doot-core/src/deploy/diff.rs @@ -8,6 +8,7 @@ pub struct DiffDisplay; impl DiffDisplay { /// Diffs two files and returns a formatted string. + #[tracing::instrument(skip_all)] pub fn diff_files(source: &PathBuf, target: &PathBuf) -> Result { let source_content = std::fs::read_to_string(source)?; let target_content = if target.exists() { @@ -37,6 +38,7 @@ impl DiffDisplay { } /// Checks if source and target differ. + #[tracing::instrument(skip_all)] pub fn has_changes(source: &PathBuf, target: &PathBuf) -> Result { if !target.exists() { return Ok(true); @@ -54,6 +56,7 @@ impl DiffDisplay { } /// Returns a unified diff format. + #[tracing::instrument(skip_all)] pub fn unified_diff(source: &PathBuf, target: &PathBuf) -> Result { let source_content = std::fs::read_to_string(source)?; let target_content = if target.exists() { diff --git a/crates/doot-core/src/deploy/linker.rs b/crates/doot-core/src/deploy/linker.rs index 0c8359b..eff0728 100644 --- a/crates/doot-core/src/deploy/linker.rs +++ b/crates/doot-core/src/deploy/linker.rs @@ -11,11 +11,13 @@ pub struct Linker { impl Linker { /// Creates a new linker. + #[tracing::instrument(skip_all)] pub fn new(config: Config) -> Self { Self { config } } /// Creates a symlink from source to target. + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] pub fn link(&self, source: &PathBuf, target: &PathBuf) -> Result { if target.is_symlink() { let current_target = std::fs::read_link(target)?; @@ -59,6 +61,7 @@ impl Linker { } /// Removes a symlink. + #[tracing::instrument(skip(self), fields(target = %target.display()))] pub fn unlink(&self, target: &PathBuf) -> Result<(), DeployError> { if target.is_symlink() && !self.config.dry_run { std::fs::remove_file(target)?; @@ -67,6 +70,7 @@ impl Linker { } /// Checks if target is linked to source. + #[tracing::instrument(level = "trace", skip(self))] pub fn is_linked(&self, source: &PathBuf, target: &PathBuf) -> bool { if !target.is_symlink() { return false; diff --git a/crates/doot-core/src/deploy/mod.rs b/crates/doot-core/src/deploy/mod.rs index cb2b9f3..bc3d54c 100644 --- a/crates/doot-core/src/deploy/mod.rs +++ b/crates/doot-core/src/deploy/mod.rs @@ -102,6 +102,7 @@ pub struct Deployer { impl Deployer { /// Creates a new deployer. + #[tracing::instrument(skip_all)] pub fn new(config: Config, sandbox: bool) -> Self { let state = StateStore::new(&config.state_file); Self { @@ -113,6 +114,7 @@ impl Deployer { } } + #[tracing::instrument(skip(self), fields(target = %target.display()))] fn check_sandbox(&self, target: &Path) -> Result<(), DeployError> { if !self.sandbox { return Ok(()); @@ -131,6 +133,7 @@ impl Deployer { } /// Deploys all dotfiles. + #[tracing::instrument(skip_all)] pub fn deploy(&mut self, dotfiles: &[DotfileConfig]) -> Result { let mut result = DeployResult { deployed: Vec::new(), @@ -162,6 +165,7 @@ impl Deployer { Ok(result) } + #[tracing::instrument(skip(self), fields(source = %dotfile.source.display(), target = %dotfile.target.display()))] fn deploy_single(&mut self, dotfile: &DotfileConfig) -> Result { let source = self.config.source_dir.join(&dotfile.source); let target = &dotfile.target; @@ -181,11 +185,10 @@ impl Deployer { } // For files or link mode, handle as before - if target.exists() && !target.is_symlink() - && !self.config.dry_run { - self.backup_existing(target)?; - std::fs::remove_file(target)?; - } + if target.exists() && !target.is_symlink() && !self.config.dry_run { + self.backup_existing(target)?; + std::fs::remove_file(target)?; + } let action = if dotfile.template { self.deploy_template(&source, target)? @@ -206,9 +209,10 @@ impl Deployer { // Set owner if specified if let Some(ref owner) = dotfile.owner - && !self.config.dry_run { - set_owner(target, owner)?; - } + && !self.config.dry_run + { + set_owner(target, owner)?; + } self.state .record_deployment_with_template(&source, target, deploy_mode, dotfile.template); @@ -220,6 +224,7 @@ impl Deployer { }) } + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] fn deploy_directory( &mut self, dotfile: &DotfileConfig, @@ -230,6 +235,10 @@ impl Deployer { use crate::state::SyncStatus; let changed_files = self.state.get_changed_files_in_dir(source, target); + tracing::trace!( + changed_count = changed_files.len(), + "directory file changes" + ); if changed_files.is_empty() { return Ok(DeployedFile { @@ -305,9 +314,10 @@ impl Deployer { // Set owner if specified (for entire directory) if let Some(ref owner) = dotfile.owner - && !self.config.dry_run { - set_owner(target, owner)?; - } + && !self.config.dry_run + { + set_owner(target, owner)?; + } // Also record the directory-level deployment for sync status checks self.state.record_deployment(source, target, deploy_mode); @@ -327,6 +337,7 @@ impl Deployer { }) } + #[tracing::instrument(level = "trace", skip(self))] fn resolve_deploy_mode(&self, dotfile: &DotfileConfig, source: &Path) -> DeployMode { let relative_path = source .strip_prefix(&self.config.source_dir) @@ -338,29 +349,33 @@ impl Deployer { doot_lang::evaluator::DeployMode::Copy => DeployMode::Copy, doot_lang::evaluator::DeployMode::Link => DeployMode::Link, }; + tracing::trace!(mode = ?base_mode, "resolved deploy mode"); match base_mode { DeployMode::Copy => { for pattern in &dotfile.link_patterns { if let Ok(p) = Pattern::new(pattern) - && p.matches(&relative_path) { - return DeployMode::Link; - } + && p.matches(&relative_path) + { + return DeployMode::Link; + } } DeployMode::Copy } DeployMode::Link => { for pattern in &dotfile.copy_patterns { if let Ok(p) = Pattern::new(pattern) - && p.matches(&relative_path) { - return DeployMode::Copy; - } + && p.matches(&relative_path) + { + return DeployMode::Copy; + } } DeployMode::Link } } } + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] fn copy_single_file(&self, source: &Path, target: &Path) -> Result { if target.exists() { let source_content = std::fs::read(source)?; @@ -384,6 +399,7 @@ impl Deployer { }) } + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] fn deploy_template( &self, source: &PathBuf, @@ -416,6 +432,7 @@ impl Deployer { }) } + #[tracing::instrument(skip(self), fields(target = %target.display()))] fn backup_existing(&self, target: &PathBuf) -> Result<(), DeployError> { let backup_path = self.config.backup_dir.join( target @@ -471,6 +488,7 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> { use doot_lang::evaluator::PermissionRule; +#[tracing::instrument(level = "trace", skip_all)] fn apply_permissions(target: &Path, rules: &[PermissionRule]) -> Result<(), DeployError> { if target.is_file() { // For single files, apply first matching rule @@ -524,10 +542,11 @@ fn apply_permissions_recursive( } PermissionRule::Pattern { pattern, mode } => { if let Ok(p) = Pattern::new(pattern) - && p.matches(&relative) { - set_file_permissions(&path, *mode)?; - break; - } + && p.matches(&relative) + { + set_file_permissions(&path, *mode)?; + break; + } } } } @@ -550,6 +569,7 @@ fn set_file_permissions(_path: &Path, _mode: u32) -> Result<(), DeployError> { } #[cfg(unix)] +#[tracing::instrument(skip_all)] fn set_owner(path: &Path, owner: &str) -> Result<(), DeployError> { use std::process::Command; @@ -586,6 +606,7 @@ fn set_owner(path: &Path, owner: &str) -> Result<(), DeployError> { } #[cfg(not(unix))] +#[tracing::instrument(skip_all)] fn set_owner(_path: &Path, _owner: &str) -> Result<(), DeployError> { Ok(()) } diff --git a/crates/doot-core/src/deploy/template.rs b/crates/doot-core/src/deploy/template.rs index 09af521..7466664 100644 --- a/crates/doot-core/src/deploy/template.rs +++ b/crates/doot-core/src/deploy/template.rs @@ -12,6 +12,7 @@ pub struct TemplateEngine { impl TemplateEngine { /// Creates a new engine with default variables and functions. + #[tracing::instrument(skip_all)] pub fn new() -> Self { let mut env = Environment::new(); @@ -30,6 +31,7 @@ impl TemplateEngine { } /// Renders a template string. + #[tracing::instrument(skip_all)] pub fn render(&self, template: &str) -> Result { // Add template to environment let tmpl = self @@ -52,6 +54,7 @@ impl Default for TemplateEngine { } /// Builds the default template variables. +#[tracing::instrument(skip_all)] fn build_default_variables() -> HashMap { let mut vars = HashMap::new(); @@ -91,9 +94,10 @@ fn build_default_variables() -> HashMap { // Detect Linux distro if std::env::consts::OS == "linux" - && let Some(distro) = detect_distro() { - vars.insert("distro".to_string(), Value::from(distro)); - } + && let Some(distro) = detect_distro() + { + vars.insert("distro".to_string(), Value::from(distro)); + } // Environment variables as a nested object let env_vars: HashMap = @@ -367,6 +371,7 @@ fn register_functions(env: &mut Environment<'static>) { } /// Expands ~ to home directory in paths. +#[tracing::instrument(level = "trace")] fn expand_path(s: &str) -> PathBuf { if let Some(stripped) = s.strip_prefix('~') { let home = dirs::home_dir().unwrap_or_default(); @@ -377,6 +382,7 @@ fn expand_path(s: &str) -> PathBuf { } /// Detects the Linux distribution. +#[tracing::instrument(level = "trace")] fn detect_distro() -> Option { if std::env::consts::OS != "linux" { return None; diff --git a/crates/doot-core/src/encryption/mod.rs b/crates/doot-core/src/encryption/mod.rs index e491be3..e13c7f2 100644 --- a/crates/doot-core/src/encryption/mod.rs +++ b/crates/doot-core/src/encryption/mod.rs @@ -40,6 +40,7 @@ impl AgeEncryption { } /// Sets the identity for decryption. + #[tracing::instrument(skip_all)] pub fn with_identity(mut self, identity_str: &str) -> Result { let identity = identity_str .parse::() @@ -49,6 +50,7 @@ impl AgeEncryption { } /// Adds a recipient public key. + #[tracing::instrument(skip_all)] pub fn add_recipient(&mut self, recipient_str: &str) -> Result<(), EncryptionError> { let recipient = recipient_str .parse::() @@ -58,6 +60,7 @@ impl AgeEncryption { } /// Encrypts data for all recipients. + #[tracing::instrument(skip_all)] pub fn encrypt(&self, data: &[u8]) -> Result, EncryptionError> { if self.recipients.is_empty() { return Err(EncryptionError::EncryptionFailed( @@ -90,6 +93,7 @@ impl AgeEncryption { } /// Decrypts data using the configured identity. + #[tracing::instrument(skip_all)] pub fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { let identity = self.identity.as_ref().ok_or_else(|| { EncryptionError::DecryptionFailed("no identity configured".to_string()) @@ -119,6 +123,7 @@ impl AgeEncryption { } /// Encrypts a file to a target path. + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] pub fn encrypt_file(&self, source: &PathBuf, target: &PathBuf) -> Result<(), EncryptionError> { let data = std::fs::read(source)?; let encrypted = self.encrypt(&data)?; @@ -127,6 +132,7 @@ impl AgeEncryption { } /// Decrypts a file to a target path. + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] pub fn decrypt_file(&self, source: &PathBuf, target: &PathBuf) -> Result<(), EncryptionError> { let data = std::fs::read(source)?; let decrypted = self.decrypt(&data)?; diff --git a/crates/doot-core/src/hooks.rs b/crates/doot-core/src/hooks.rs index 59c2b4c..51070cc 100644 --- a/crates/doot-core/src/hooks.rs +++ b/crates/doot-core/src/hooks.rs @@ -49,6 +49,7 @@ impl HookRunner { } /// Runs all hooks for a given stage. + #[tracing::instrument(skip(self))] pub fn run_stage(&self, stage: HookStage) -> Result<(), HookError> { for hook in self.hooks.iter().filter(|h| h.stage == stage) { self.run_hook(hook)?; @@ -56,6 +57,7 @@ impl HookRunner { Ok(()) } + #[tracing::instrument(skip(self), fields(command = %hook.command))] fn run_hook(&self, hook: &Hook) -> Result<(), HookError> { if self.dry_run { println!("[dry-run] would run: {}", hook.command); diff --git a/crates/doot-core/src/os.rs b/crates/doot-core/src/os.rs index 95c3bcb..d98887e 100644 --- a/crates/doot-core/src/os.rs +++ b/crates/doot-core/src/os.rs @@ -25,6 +25,7 @@ pub enum OsType { impl OsInfo { /// Detects the current operating system. + #[tracing::instrument] pub fn detect() -> Self { let info = os_info::get(); @@ -79,6 +80,7 @@ impl OsInfo { } /// Detects the available package manager. + #[tracing::instrument(skip(self))] pub fn detect_package_manager(&self) -> Option<&'static str> { match self.os_type { OsType::MacOS => Some("brew"), @@ -116,6 +118,7 @@ impl Default for OsInfo { static COMMAND_CACHE: OnceLock>> = OnceLock::new(); /// Checks if a command exists in PATH or common bin directories (cached). +#[tracing::instrument(level = "trace")] fn command_exists(cmd: &str) -> bool { let cache = COMMAND_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new())); let mut cache = cache.lock().unwrap(); diff --git a/crates/doot-core/src/package/apt.rs b/crates/doot-core/src/package/apt.rs index 17a2dcd..46a6a1d 100644 --- a/crates/doot-core/src/package/apt.rs +++ b/crates/doot-core/src/package/apt.rs @@ -25,6 +25,7 @@ impl Apt { self } + #[tracing::instrument(skip(self))] fn run_apt(&self, args: &[&str]) -> Result<(), PackageError> { if self.dry_run { let prefix = if self.use_sudo { "sudo " } else { "" }; @@ -48,6 +49,7 @@ impl Apt { Ok(()) } + #[tracing::instrument(skip(self, password))] fn run_apt_with_password(&self, args: &[&str], password: &str) -> Result<(), PackageError> { if self.dry_run { println!("[dry-run] sudo apt {}", args.join(" ")); diff --git a/crates/doot-core/src/package/brew.rs b/crates/doot-core/src/package/brew.rs index 2bb3ec1..b310828 100644 --- a/crates/doot-core/src/package/brew.rs +++ b/crates/doot-core/src/package/brew.rs @@ -15,6 +15,7 @@ impl Brew { self } + #[tracing::instrument(skip(self))] fn run_brew(&self, args: &[&str]) -> Result<(), PackageError> { if self.dry_run { println!("[dry-run] brew {}", args.join(" ")); diff --git a/crates/doot-core/src/package/mod.rs b/crates/doot-core/src/package/mod.rs index 428d83f..3cae1ed 100644 --- a/crates/doot-core/src/package/mod.rs +++ b/crates/doot-core/src/package/mod.rs @@ -134,6 +134,7 @@ impl PackageManager for MockPackageManager { } /// Detects the available package manager. +#[tracing::instrument] pub fn detect_package_manager() -> Option> { if is_test_mode() { return Some(Box::new(MockPackageManager::new())); @@ -151,6 +152,7 @@ pub fn detect_package_manager() -> Option> { } /// Gets a package manager by name. +#[tracing::instrument] pub fn get_package_manager(name: &str) -> Option> { if is_test_mode() { return Some(Box::new(MockPackageManager::new())); diff --git a/crates/doot-core/src/package/pacman.rs b/crates/doot-core/src/package/pacman.rs index 8f283b0..9462620 100644 --- a/crates/doot-core/src/package/pacman.rs +++ b/crates/doot-core/src/package/pacman.rs @@ -25,6 +25,7 @@ impl Pacman { self } + #[tracing::instrument(skip(self))] fn run_pacman(&self, args: &[&str]) -> Result<(), PackageError> { if self.dry_run { let prefix = if self.use_sudo { "sudo " } else { "" }; @@ -48,6 +49,7 @@ impl Pacman { Ok(()) } + #[tracing::instrument(skip(self, password))] fn run_pacman_with_password(&self, args: &[&str], password: &str) -> Result<(), PackageError> { if self.dry_run { println!("[dry-run] sudo pacman {}", args.join(" ")); diff --git a/crates/doot-core/src/package/yay.rs b/crates/doot-core/src/package/yay.rs index 74578be..6abb257 100644 --- a/crates/doot-core/src/package/yay.rs +++ b/crates/doot-core/src/package/yay.rs @@ -15,6 +15,7 @@ impl Yay { self } + #[tracing::instrument(skip(self))] fn run_yay(&self, args: &[&str]) -> Result<(), PackageError> { if self.dry_run { println!("[dry-run] yay {}", args.join(" ")); diff --git a/crates/doot-core/src/state/snapshot.rs b/crates/doot-core/src/state/snapshot.rs index db58651..2c42f54 100644 --- a/crates/doot-core/src/state/snapshot.rs +++ b/crates/doot-core/src/state/snapshot.rs @@ -12,6 +12,7 @@ pub struct Snapshot { impl Snapshot { /// Creates and saves a new snapshot. + #[tracing::instrument(skip_all, fields(name))] pub fn create(name: &str, state: &State, snapshot_dir: &Path) -> Result { let created_at = chrono_now(); let snapshot = Self { @@ -25,6 +26,7 @@ impl Snapshot { } /// Loads a snapshot by name. + #[tracing::instrument(skip_all, fields(name))] pub fn load(name: &str, snapshot_dir: &Path) -> Result { let path = snapshot_dir.join(format!("{}.json", name)); let content = std::fs::read_to_string(&path)?; @@ -38,6 +40,7 @@ impl Snapshot { } /// Saves the snapshot to disk. + #[tracing::instrument(skip(self))] pub fn save(&self, snapshot_dir: &Path) -> Result<(), StateError> { std::fs::create_dir_all(snapshot_dir)?; let path = snapshot_dir.join(format!("{}.json", self.name)); @@ -47,6 +50,7 @@ impl Snapshot { } /// Lists all snapshots. + #[tracing::instrument(skip_all)] pub fn list(snapshot_dir: &Path) -> Result, StateError> { if !snapshot_dir.exists() { return Ok(Vec::new()); @@ -57,9 +61,10 @@ impl Snapshot { let entry = entry?; let path = entry.path(); if path.extension().map(|e| e == "json").unwrap_or(false) - && let Some(name) = path.file_stem() { - snapshots.push(name.to_string_lossy().to_string()); - } + && let Some(name) = path.file_stem() + { + snapshots.push(name.to_string_lossy().to_string()); + } } snapshots.sort(); @@ -67,6 +72,7 @@ impl Snapshot { } /// Deletes a snapshot. + #[tracing::instrument(skip_all, fields(name))] pub fn delete(name: &str, snapshot_dir: &Path) -> Result<(), StateError> { let path = snapshot_dir.join(format!("{}.json", name)); if path.exists() { diff --git a/crates/doot-core/src/state/store.rs b/crates/doot-core/src/state/store.rs index e7e3662..8c470a5 100644 --- a/crates/doot-core/src/state/store.rs +++ b/crates/doot-core/src/state/store.rs @@ -75,6 +75,7 @@ pub struct StateStore { impl StateStore { /// Loads or creates a state store at the given path. + #[tracing::instrument(skip_all, fields(path = %path.display()))] pub fn new(path: &Path) -> Self { let state = if path.exists() { std::fs::read_to_string(path) @@ -93,11 +94,13 @@ impl StateStore { } /// Records a deployment with both source and target hashes. + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] pub fn record_deployment(&mut self, source: &Path, target: &Path, mode: DeployMode) { self.record_deployment_with_template(source, target, mode, false); } /// Records a deployment with template flag. + #[tracing::instrument(skip(self), fields(source = %source.display(), target = %target.display()))] pub fn record_deployment_with_template( &mut self, source: &Path, @@ -125,11 +128,13 @@ impl StateStore { } /// Checks sync status by comparing current hashes with recorded state. + #[tracing::instrument(level = "trace", skip(self))] pub fn check_sync_status(&self, source: &Path, target: &Path) -> SyncStatus { self.check_sync_status_with_config(source, target, None, None) } /// Checks sync status, also detecting if template flag changed in config. + #[tracing::instrument(level = "trace", skip(self))] pub fn check_sync_status_with_template( &self, source: &Path, @@ -140,6 +145,7 @@ impl StateStore { } /// Checks sync status, also detecting if config flags changed. + #[tracing::instrument(level = "trace", skip(self))] pub fn check_sync_status_with_config( &self, source: &Path, @@ -153,15 +159,17 @@ impl StateStore { // If template flag changed in config, force re-deploy if let Some(is_template) = current_template - && is_template != record.template { - return SyncStatus::SourceChanged; - } + && is_template != record.template + { + return SyncStatus::SourceChanged; + } // If deploy mode changed in config, force re-deploy if let Some(mode) = current_mode - && mode != record.mode { - return SyncStatus::SourceChanged; - } + && mode != record.mode + { + return SyncStatus::SourceChanged; + } if !source.exists() { return SyncStatus::SourceMissing; @@ -190,6 +198,7 @@ impl StateStore { let current_source_hash = hash_path(source); let current_target_hash = hash_path(target); + tracing::trace!(source_hash = %current_source_hash, target_hash = %current_target_hash, "computed hashes"); let source_changed = current_source_hash != record.source_hash; let target_changed = current_target_hash != record.target_hash; @@ -203,6 +212,7 @@ impl StateStore { } /// Records a package installation. + #[tracing::instrument(skip(self))] pub fn record_package(&mut self, name: &str, manager: &str) { let record = PackageRecord { name: name.to_string(), @@ -242,6 +252,7 @@ impl StateStore { } /// Saves state to disk if dirty. + #[tracing::instrument(skip(self))] pub fn save(&mut self) -> Result<(), StateError> { if !self.dirty { return Ok(()); @@ -274,6 +285,7 @@ impl StateStore { } /// Records a directory deployment by tracking each file individually. + #[tracing::instrument(skip(self))] pub fn record_directory_deployment( &mut self, source_dir: &Path, @@ -293,6 +305,7 @@ impl StateStore { /// Returns files that have changed in a directory. /// Returns (source_path, target_path, status) for each changed file. + #[tracing::instrument(skip(self), fields(source_dir = %source_dir.display(), target_dir = %target_dir.display()))] pub fn get_changed_files_in_dir( &self, source_dir: &Path, @@ -339,6 +352,7 @@ impl StateStore { } /// Removes all deployment records for files within a directory. + #[tracing::instrument(skip(self))] pub fn remove_directory_deployment(&mut self, target_dir: &Path) { let target_prefix = target_dir.display().to_string(); let to_remove: Vec = self diff --git a/crates/doot-lang/Cargo.toml b/crates/doot-lang/Cargo.toml index b8a92a9..149377a 100644 --- a/crates/doot-lang/Cargo.toml +++ b/crates/doot-lang/Cargo.toml @@ -25,6 +25,7 @@ glob = "0.3" hostname = "0.4" age = "0.10" ordered-float = "5" +tracing.workspace = true [dev-dependencies] tempfile = "3" diff --git a/crates/doot-lang/src/builtins/async_ops.rs b/crates/doot-lang/src/builtins/async_ops.rs index f34e1e4..b9de693 100644 --- a/crates/doot-lang/src/builtins/async_ops.rs +++ b/crates/doot-lang/src/builtins/async_ops.rs @@ -1,13 +1,16 @@ use crate::evaluator::{EvalError, Value}; +#[tracing::instrument(level = "trace", skip_all)] pub fn all(args: &[Value]) -> Result { Ok(Value::List(args.to_vec())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn race(args: &[Value]) -> Result { Ok(args.first().cloned().unwrap_or(Value::None)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn fetch(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, @@ -32,6 +35,7 @@ pub fn fetch(args: &[Value]) -> Result { }) } +#[tracing::instrument(level = "trace", skip_all)] pub fn fetch_json(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, @@ -56,6 +60,7 @@ pub fn fetch_json(args: &[Value]) -> Result { }) } +#[tracing::instrument(level = "trace", skip_all)] pub fn fetch_bytes(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, @@ -81,6 +86,7 @@ pub fn fetch_bytes(args: &[Value]) -> Result { }) } +#[tracing::instrument(level = "trace", skip_all)] pub fn post(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, @@ -111,6 +117,7 @@ pub fn post(args: &[Value]) -> Result { }) } +#[tracing::instrument(level = "trace", skip_all)] pub fn post_json(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, @@ -140,6 +147,7 @@ pub fn post_json(args: &[Value]) -> Result { }) } +#[tracing::instrument(level = "trace", skip_all)] pub fn download(args: &[Value]) -> Result { let url = match args.first() { Some(Value::Str(s)) => s, diff --git a/crates/doot-lang/src/builtins/collections.rs b/crates/doot-lang/src/builtins/collections.rs index 0c6fc7d..c210f2b 100644 --- a/crates/doot-lang/src/builtins/collections.rs +++ b/crates/doot-lang/src/builtins/collections.rs @@ -1,6 +1,7 @@ use crate::ast::Expr; use crate::evaluator::{EvalError, Evaluator, Value}; +#[tracing::instrument(level = "trace", skip_all)] pub fn map(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result { let list = match args.first() { Some(Value::List(items)) => items.clone(), @@ -43,6 +44,7 @@ pub fn map(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result< } } +#[tracing::instrument(level = "trace", skip_all)] pub fn filter( eval: &mut Evaluator, args: &[Value], @@ -95,6 +97,7 @@ pub fn filter( } } +#[tracing::instrument(level = "trace", skip_all)] pub fn fold(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result { let list = match args.first() { Some(Value::List(items)) => items.clone(), @@ -140,6 +143,7 @@ pub fn fold(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result } } +#[tracing::instrument(level = "trace", skip_all)] pub fn flatten(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items, @@ -156,6 +160,7 @@ pub fn flatten(args: &[Value]) -> Result { Ok(Value::List(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn concat(args: &[Value]) -> Result { let mut result = Vec::new(); for arg in args { @@ -167,6 +172,7 @@ pub fn concat(args: &[Value]) -> Result { Ok(Value::List(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn zip(args: &[Value]) -> Result { if args.len() < 2 { return Err(EvalError::TypeError( @@ -194,6 +200,7 @@ pub fn zip(args: &[Value]) -> Result { Ok(Value::List(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn enumerate(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items, @@ -209,6 +216,7 @@ pub fn enumerate(args: &[Value]) -> Result { Ok(Value::List(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn first(args: &[Value]) -> Result { match args.first() { Some(Value::List(items)) => Ok(items.first().cloned().unwrap_or(Value::None)), @@ -216,6 +224,7 @@ pub fn first(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn last(args: &[Value]) -> Result { match args.first() { Some(Value::List(items)) => Ok(items.last().cloned().unwrap_or(Value::None)), @@ -223,6 +232,7 @@ pub fn last(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn len(args: &[Value]) -> Result { match args.first() { Some(Value::List(items)) => Ok(Value::Int(items.len() as i64)), @@ -233,6 +243,7 @@ pub fn len(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn contains(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items, @@ -243,6 +254,7 @@ pub fn contains(args: &[Value]) -> Result { Ok(Value::Bool(list.iter().any(|v| values_equal(v, needle)))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn unique(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items, @@ -262,6 +274,7 @@ pub fn unique(args: &[Value]) -> Result { Ok(Value::List(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn sort(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items.clone(), @@ -285,6 +298,7 @@ pub fn sort(args: &[Value]) -> Result { Ok(Value::List(sortable.into_iter().map(|(v, _)| v).collect())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn sort_by( eval: &mut Evaluator, args: &[Value], @@ -316,6 +330,7 @@ pub fn sort_by( } } +#[tracing::instrument(level = "trace", skip_all)] pub fn reverse(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items.clone(), @@ -327,6 +342,7 @@ pub fn reverse(args: &[Value]) -> Result { Ok(Value::List(reversed)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn seq(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result { let list = match args.first() { Some(Value::List(items)) => items.clone(), @@ -351,6 +367,7 @@ pub fn seq(eval: &mut Evaluator, args: &[Value], _arg_exprs: &[Expr]) -> Result< } } +#[tracing::instrument(level = "trace", skip_all)] pub fn batch( eval: &mut Evaluator, args: &[Value], diff --git a/crates/doot-lang/src/builtins/crypto.rs b/crates/doot-lang/src/builtins/crypto.rs index 83a8aa5..b6d3f16 100644 --- a/crates/doot-lang/src/builtins/crypto.rs +++ b/crates/doot-lang/src/builtins/crypto.rs @@ -1,6 +1,7 @@ use crate::evaluator::{EvalError, Value}; use std::path::PathBuf; +#[tracing::instrument(level = "trace", skip_all)] pub fn hash_file(args: &[Value]) -> Result { let path = match args.first() { Some(Value::Path(p)) => p.clone(), @@ -13,6 +14,7 @@ pub fn hash_file(args: &[Value]) -> Result { Ok(Value::Str(hash.to_hex().to_string())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn hash_str(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -27,6 +29,7 @@ pub fn hash_str(args: &[Value]) -> Result { Ok(Value::Str(hash.to_hex().to_string())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn encrypt_age(args: &[Value]) -> Result { let content = match args.first() { Some(Value::Str(s)) => s, @@ -69,6 +72,7 @@ pub fn encrypt_age(args: &[Value]) -> Result { Ok(Value::Str(base64_encode(&encrypted))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn decrypt_age(args: &[Value]) -> Result { let encrypted = match args.first() { Some(Value::Str(s)) => s, diff --git a/crates/doot-lang/src/builtins/io.rs b/crates/doot-lang/src/builtins/io.rs index 52046ad..ecbe232 100644 --- a/crates/doot-lang/src/builtins/io.rs +++ b/crates/doot-lang/src/builtins/io.rs @@ -3,12 +3,14 @@ use std::path::PathBuf; use std::process::Command; use walkdir::WalkDir; +#[tracing::instrument(level = "trace", skip_all)] pub fn read_file(args: &[Value]) -> Result { let path = get_path(args)?; let content = std::fs::read_to_string(&path)?; Ok(Value::Str(content)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn read_file_lines(args: &[Value]) -> Result { let path = get_path(args)?; let content = std::fs::read_to_string(&path)?; @@ -16,6 +18,7 @@ pub fn read_file_lines(args: &[Value]) -> Result { Ok(Value::List(lines)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn write_file(args: &[Value]) -> Result { let path = get_path(args)?; let content = match args.get(1) { @@ -30,6 +33,7 @@ pub fn write_file(args: &[Value]) -> Result { Ok(Value::Bool(true)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn copy_file(args: &[Value]) -> Result { let src = get_path(args)?; let dst = match args.get(1) { @@ -45,28 +49,33 @@ pub fn copy_file(args: &[Value]) -> Result { Ok(Value::Bool(true)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn delete_file(args: &[Value]) -> Result { let path = get_path(args)?; std::fs::remove_file(&path)?; Ok(Value::Bool(true)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn file_exists(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Bool(path.is_file())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn dir_exists(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Bool(path.is_dir())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn create_dir_all(args: &[Value]) -> Result { let path = get_path(args)?; std::fs::create_dir_all(&path)?; Ok(Value::Bool(true)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn list_dir(args: &[Value]) -> Result { let path = get_path(args)?; let entries: Vec = std::fs::read_dir(&path)? @@ -76,6 +85,7 @@ pub fn list_dir(args: &[Value]) -> Result { Ok(Value::List(entries)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn glob_files(args: &[Value]) -> Result { let pattern = match args.first() { Some(Value::Str(s)) => s, @@ -95,6 +105,7 @@ pub fn glob_files(args: &[Value]) -> Result { Ok(Value::List(entries)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn walk_dir(args: &[Value]) -> Result { let path = get_path(args)?; let entries: Vec = WalkDir::new(&path) @@ -105,10 +116,12 @@ pub fn walk_dir(args: &[Value]) -> Result { Ok(Value::List(entries)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn temp_dir() -> Result { Ok(Value::Path(std::env::temp_dir())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn temp_file(args: &[Value]) -> Result { let prefix = match args.first() { Some(Value::Str(s)) => s.as_str(), @@ -122,17 +135,20 @@ pub fn temp_file(args: &[Value]) -> Result { Ok(Value::Path(path)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn is_symlink(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Bool(path.is_symlink())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn read_link(args: &[Value]) -> Result { let path = get_path(args)?; let target = std::fs::read_link(&path)?; Ok(Value::Path(target)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn path_join(args: &[Value]) -> Result { let mut result = PathBuf::new(); for arg in args { @@ -149,6 +165,7 @@ pub fn path_join(args: &[Value]) -> Result { Ok(Value::Path(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn path_parent(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Path( @@ -156,6 +173,7 @@ pub fn path_parent(args: &[Value]) -> Result { )) } +#[tracing::instrument(level = "trace", skip_all)] pub fn path_filename(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Str( @@ -165,6 +183,7 @@ pub fn path_filename(args: &[Value]) -> Result { )) } +#[tracing::instrument(level = "trace", skip_all)] pub fn path_extension(args: &[Value]) -> Result { let path = get_path(args)?; Ok(Value::Str( @@ -174,14 +193,17 @@ pub fn path_extension(args: &[Value]) -> Result { )) } +#[tracing::instrument(level = "trace", skip_all)] pub fn home() -> Result { Ok(Value::Path(dirs::home_dir().unwrap_or_default())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn config_dir() -> Result { Ok(Value::Path(dirs::config_dir().unwrap_or_default())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn config_path(args: &[Value]) -> Result { let app = match args.first() { Some(Value::Str(s)) => s, @@ -195,14 +217,17 @@ pub fn config_path(args: &[Value]) -> Result { Ok(Value::Path(config.join(app))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn data_dir() -> Result { Ok(Value::Path(dirs::data_dir().unwrap_or_default())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn cache_dir() -> Result { Ok(Value::Path(dirs::cache_dir().unwrap_or_default())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn exec(args: &[Value]) -> Result { let cmd = match args.first() { Some(Value::Str(s)) => s, @@ -220,6 +245,7 @@ pub fn exec(args: &[Value]) -> Result { )) } +#[tracing::instrument(level = "trace", skip_all)] pub fn exec_with_status(args: &[Value]) -> Result { let cmd = match args.first() { Some(Value::Str(s)) => s, @@ -235,10 +261,12 @@ pub fn exec_with_status(args: &[Value]) -> Result { Ok(Value::Int(status.code().unwrap_or(-1) as i64)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn shell(args: &[Value]) -> Result { exec(args) } +#[tracing::instrument(level = "trace", skip_all)] pub fn which(args: &[Value]) -> Result { let cmd = match args.first() { Some(Value::Str(s)) => s, @@ -259,12 +287,14 @@ pub fn which(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn to_json(args: &[Value]) -> Result { let val = args.first().unwrap_or(&Value::None); let json = value_to_json(val); Ok(Value::Str(json.to_string())) } +#[tracing::instrument(level = "trace", skip_all)] pub fn from_json(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -281,6 +311,7 @@ pub fn from_json(args: &[Value]) -> Result { Ok(json_to_value(&json)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn to_toml(args: &[Value]) -> Result { let val = args.first().unwrap_or(&Value::None); let toml_val = value_to_toml(val); @@ -289,6 +320,7 @@ pub fn to_toml(args: &[Value]) -> Result { Ok(Value::Str(s)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn from_toml(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -305,6 +337,7 @@ pub fn from_toml(args: &[Value]) -> Result { Ok(toml_to_value(&toml_val)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn to_yaml(args: &[Value]) -> Result { let val = args.first().unwrap_or(&Value::None); let json = value_to_json(val); @@ -313,6 +346,7 @@ pub fn to_yaml(args: &[Value]) -> Result { )) } +#[tracing::instrument(level = "trace", skip_all)] pub fn from_yaml(args: &[Value]) -> Result { from_json(args) } diff --git a/crates/doot-lang/src/builtins/mod.rs b/crates/doot-lang/src/builtins/mod.rs index 4ee4c0d..7fbf398 100644 --- a/crates/doot-lang/src/builtins/mod.rs +++ b/crates/doot-lang/src/builtins/mod.rs @@ -10,6 +10,7 @@ use crate::ast::Expr; use crate::evaluator::{EvalError, Evaluator, Value}; /// Dispatches a built-in function call. +#[tracing::instrument(level = "trace", skip_all, fields(name))] pub fn call_builtin( eval: &mut Evaluator, name: &str, @@ -126,6 +127,7 @@ pub fn call_builtin( } /// Dispatches a method call on a value. +#[tracing::instrument(level = "trace", skip_all, fields(method))] pub fn call_method( eval: &mut Evaluator, obj: &Value, @@ -216,9 +218,10 @@ pub fn call_method( } "replace" => { if args.len() >= 2 - && let (Value::Str(from), Value::Str(to)) = (&args[0], &args[1]) { - return Ok(Value::Str(s.replace(from, to))); - } + && let (Value::Str(from), Value::Str(to)) = (&args[0], &args[1]) + { + return Ok(Value::Str(s.replace(from, to))); + } Ok(Value::Str(s.clone())) } "starts_with" => { @@ -286,9 +289,10 @@ pub fn call_method( } } if let Some(field) = fields.get(method) - && let Value::Function(func, env) = field { - return eval.call_function(func, env, args); - } + && let Value::Function(func, env) = field + { + return eval.call_function(func, env, args); + } Err(EvalError::FieldNotFound { ty: name.clone(), field: method.to_string(), diff --git a/crates/doot-lang/src/builtins/strings.rs b/crates/doot-lang/src/builtins/strings.rs index 787e734..dc484f2 100644 --- a/crates/doot-lang/src/builtins/strings.rs +++ b/crates/doot-lang/src/builtins/strings.rs @@ -1,5 +1,6 @@ use crate::evaluator::{EvalError, Value}; +#[tracing::instrument(level = "trace", skip_all)] pub fn join(args: &[Value]) -> Result { let list = match args.first() { Some(Value::List(items)) => items, @@ -20,6 +21,7 @@ pub fn join(args: &[Value]) -> Result { Ok(Value::Str(result)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn split(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -35,6 +37,7 @@ pub fn split(args: &[Value]) -> Result { Ok(Value::List(parts)) } +#[tracing::instrument(level = "trace", skip_all)] pub fn upper(args: &[Value]) -> Result { match args.first() { Some(Value::Str(s)) => Ok(Value::Str(s.to_uppercase())), @@ -42,6 +45,7 @@ pub fn upper(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn lower(args: &[Value]) -> Result { match args.first() { Some(Value::Str(s)) => Ok(Value::Str(s.to_lowercase())), @@ -49,6 +53,7 @@ pub fn lower(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn trim(args: &[Value]) -> Result { match args.first() { Some(Value::Str(s)) => Ok(Value::Str(s.trim().to_string())), @@ -56,6 +61,7 @@ pub fn trim(args: &[Value]) -> Result { } } +#[tracing::instrument(level = "trace", skip_all)] pub fn replace(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -83,6 +89,7 @@ pub fn replace(args: &[Value]) -> Result { Ok(Value::Str(s.replace(from.as_str(), to.as_str()))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn starts_with(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -105,6 +112,7 @@ pub fn starts_with(args: &[Value]) -> Result { Ok(Value::Bool(s.starts_with(prefix.as_str()))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn ends_with(args: &[Value]) -> Result { let s = match args.first() { Some(Value::Str(s)) => s, @@ -127,6 +135,7 @@ pub fn ends_with(args: &[Value]) -> Result { Ok(Value::Bool(s.ends_with(suffix.as_str()))) } +#[tracing::instrument(level = "trace", skip_all)] pub fn format(args: &[Value]) -> Result { let template = match args.first() { Some(Value::Str(s)) => s.clone(), diff --git a/crates/doot-lang/src/evaluator.rs b/crates/doot-lang/src/evaluator.rs index 37f1261..767ade8 100644 --- a/crates/doot-lang/src/evaluator.rs +++ b/crates/doot-lang/src/evaluator.rs @@ -335,6 +335,7 @@ pub struct Evaluator { impl Evaluator { /// Creates a new evaluator with built-in bindings. + #[tracing::instrument(level = "trace")] pub fn new() -> Self { let mut env = Env::new(); Self::init_builtins(&mut env); @@ -344,6 +345,7 @@ impl Evaluator { } } + #[tracing::instrument(level = "trace", skip_all)] fn init_builtins(env: &mut Env) { // Register the Os enum so Os::Linux, Os::MacOS, etc. can be used env.define_enum( @@ -387,6 +389,7 @@ impl Evaluator { } /// Evaluates the program and returns collected configuration. + #[tracing::instrument(level = "trace", skip_all)] pub fn eval(&mut self, program: &Program) -> Result { for stmt in &program.statements { self.eval_statement(&stmt.node)?; @@ -395,6 +398,7 @@ impl Evaluator { } /// Returns all variables as environment variables for hooks. + #[tracing::instrument(level = "trace", skip(self))] pub fn get_hook_env(&self) -> std::collections::HashMap { let mut vars = self.env.get_all_variables(); @@ -417,22 +421,26 @@ impl Evaluator { vars } + #[tracing::instrument(level = "trace", skip_all)] fn eval_statement(&mut self, stmt: &Statement) -> Result, EvalError> { match stmt { Statement::VarDecl(decl) => { + tracing::trace!(name = %decl.name, "eval var declaration"); let value = self.eval_expr(&decl.value)?; // Handle special config variables if decl.name == "sandbox" - && let Value::Bool(b) = &value { - self.result.sandbox = *b; - } + && let Value::Bool(b) = &value + { + self.result.sandbox = *b; + } self.env.define(decl.name.clone(), value); Ok(None) } Statement::FnDecl(decl) => { + tracing::trace!(name = %decl.name, "eval fn declaration"); self.env .define_function(decl.name.clone(), decl.clone(), self.env.clone()); Ok(None) @@ -526,6 +534,7 @@ impl Evaluator { } Statement::Dotfile(dotfile) => { + tracing::trace!("eval dotfile"); if let Some(ref when) = dotfile.when { let cond = self.eval_expr(when)?; if !cond.is_truthy() { @@ -569,6 +578,7 @@ impl Evaluator { } Statement::Package(pkg) => { + tracing::trace!("eval package"); if let Some(ref when) = pkg.when { let cond = self.eval_expr(when)?; if !cond.is_truthy() { @@ -626,6 +636,7 @@ impl Evaluator { } Statement::Hook(hook) => { + tracing::trace!("eval hook"); if let Some(ref when) = hook.when { let cond = self.eval_expr(when)?; if !cond.is_truthy() { @@ -659,6 +670,7 @@ impl Evaluator { } } + #[tracing::instrument(level = "trace", skip_all)] fn eval_expr(&mut self, expr: &Expr) -> Result { match expr { Expr::Literal(lit) => Ok(match lit { @@ -884,6 +896,7 @@ impl Evaluator { } } + #[tracing::instrument(level = "trace", skip_all)] fn eval_binary_op(&self, left: &Value, op: &BinOp, right: &Value) -> Result { match op { BinOp::Add => match (left, right) { @@ -1048,6 +1061,7 @@ impl Evaluator { } } + #[tracing::instrument(level = "trace", skip_all)] fn pattern_matches(&self, pattern: &Pattern, value: &Value) -> bool { match pattern { Pattern::Wildcard => true, @@ -1067,6 +1081,7 @@ impl Evaluator { } } + #[tracing::instrument(level = "trace", skip_all, fields(name = %func.name))] pub fn call_function( &mut self, func: &FnDecl, @@ -1094,6 +1109,7 @@ impl Evaluator { Ok(result) } + #[tracing::instrument(level = "trace", skip_all)] fn call_lambda( &mut self, params: &[FnParam], @@ -1115,6 +1131,7 @@ impl Evaluator { Ok(result) } + #[tracing::instrument(level = "trace", skip_all, fields(name))] fn call_builtin( &mut self, name: &str, @@ -1124,6 +1141,7 @@ impl Evaluator { builtins::call_builtin(self, name, args, arg_exprs) } + #[tracing::instrument(level = "trace", skip_all, fields(method))] fn call_method( &mut self, obj: &Value, @@ -1134,6 +1152,7 @@ impl Evaluator { builtins::call_method(self, obj, method, args, arg_exprs) } + #[tracing::instrument(level = "trace", skip_all)] fn eval_to_path(&mut self, expr: &Expr) -> Result { let val = self.eval_expr(expr)?; match val { @@ -1154,12 +1173,14 @@ impl Evaluator { } /// Returns DOOT_HOME if set, otherwise the real home directory. + #[tracing::instrument(level = "trace")] fn home_dir() -> PathBuf { std::env::var("DOOT_HOME") .map(PathBuf::from) .unwrap_or_else(|_| dirs::home_dir().unwrap_or_default()) } + #[tracing::instrument(level = "trace", skip_all)] fn eval_to_string(&mut self, expr: &Expr) -> Result { let val = self.eval_expr(expr)?; Ok(val.to_string_repr()) @@ -1181,6 +1202,7 @@ impl Default for Evaluator { } impl Evaluator { + #[tracing::instrument(level = "trace", skip_all)] pub fn eval_in_env(&mut self, expr: &Expr, env: Env) -> Result { let old_env = std::mem::replace(&mut self.env, env); let result = self.eval_expr(expr); @@ -1188,6 +1210,7 @@ impl Evaluator { result } + #[tracing::instrument(level = "trace", skip_all, fields(name = %func.name))] pub fn call_fn( &mut self, func: &FnDecl, diff --git a/crates/doot-lang/src/lexer.rs b/crates/doot-lang/src/lexer.rs index 9d6b6f1..e4e512c 100644 --- a/crates/doot-lang/src/lexer.rs +++ b/crates/doot-lang/src/lexer.rs @@ -333,12 +333,14 @@ impl Lexer { } /// Tokenizes the input string with indentation processing. + #[tracing::instrument(skip_all)] pub fn lex(input: &str) -> Result>, Vec>> { let tokens = Self::lexer().parse(input)?; Ok(Self::process_indentation(tokens)) } /// Converts whitespace into indent/dedent tokens. + #[tracing::instrument(level = "trace", skip_all)] fn process_indentation(tokens: Vec>) -> Vec> { let mut result = Vec::new(); let mut indent_stack = vec![0usize]; diff --git a/crates/doot-lang/src/macros.rs b/crates/doot-lang/src/macros.rs index dbda61a..bfd7806 100644 --- a/crates/doot-lang/src/macros.rs +++ b/crates/doot-lang/src/macros.rs @@ -10,6 +10,7 @@ pub struct MacroExpander { impl MacroExpander { /// Creates a new macro expander. + #[tracing::instrument(level = "trace")] pub fn new() -> Self { Self { macros: HashMap::new(), @@ -17,11 +18,13 @@ impl MacroExpander { } /// Registers a macro definition. + #[tracing::instrument(level = "trace", skip(self), fields(name = %decl.name))] pub fn register(&mut self, decl: MacroDecl) { self.macros.insert(decl.name.clone(), decl); } /// Expands a macro call into statements. + #[tracing::instrument(level = "trace", skip(self), fields(name = %call.name))] pub fn expand(&self, call: &MacroCall) -> Option>> { let decl = self.macros.get(&call.name)?; @@ -44,6 +47,7 @@ impl MacroExpander { Some(expanded) } + #[tracing::instrument(level = "trace", skip_all)] fn substitute_statement(&self, stmt: &Statement, subs: &HashMap) -> Statement { match stmt { Statement::VarDecl(decl) => Statement::VarDecl(VarDecl { @@ -121,6 +125,7 @@ impl MacroExpander { } } + #[tracing::instrument(level = "trace", skip_all)] fn substitute_expr(&self, expr: &Expr, subs: &HashMap) -> Expr { match expr { Expr::Ident(name) => { diff --git a/crates/doot-lang/src/parser.rs b/crates/doot-lang/src/parser.rs index 81f8d9e..504129d 100644 --- a/crates/doot-lang/src/parser.rs +++ b/crates/doot-lang/src/parser.rs @@ -13,6 +13,7 @@ type ParserInput = crate::lexer::Spanned; impl Parser { /// Parses a token stream into a program AST. + #[tracing::instrument(skip_all)] pub fn parse(tokens: Vec) -> Result>> { let stream = tokens .into_iter() @@ -888,9 +889,10 @@ impl Parser { } if parts.len() == 1 - && let InterpolatedPart::Literal(s) = &parts[0] { - return Expr::Literal(Literal::Str(s.clone())); - } + && let InterpolatedPart::Literal(s) = &parts[0] + { + return Expr::Literal(Literal::Str(s.clone())); + } Expr::Interpolated(parts) } diff --git a/crates/doot-lang/src/planner/executor.rs b/crates/doot-lang/src/planner/executor.rs index f199f31..6506bb4 100644 --- a/crates/doot-lang/src/planner/executor.rs +++ b/crates/doot-lang/src/planner/executor.rs @@ -47,6 +47,7 @@ pub struct Executor { impl Executor { /// Creates a new executor. + #[tracing::instrument(level = "trace", skip_all)] pub fn new(graph: DependencyGraph, handler: H) -> Self { Self { graph, @@ -62,6 +63,7 @@ impl Executor { } /// Executes tasks sequentially. + #[tracing::instrument(skip(self))] pub fn execute_sequential(&self) -> Result { let order = self .graph @@ -85,6 +87,7 @@ impl Executor { } /// Executes tasks in parallel batches. + #[tracing::instrument(skip(self))] pub fn execute_parallel(&self) -> Result { let batches = self.graph @@ -125,6 +128,7 @@ impl Executor { Ok(Arc::try_unwrap(report).unwrap().into_inner().unwrap()) } + #[tracing::instrument(level = "trace", skip(self))] fn execute_node(&self, node: &Node) -> TaskResult { if self.dry_run { return Ok(()); diff --git a/crates/doot-lang/src/planner/scheduler.rs b/crates/doot-lang/src/planner/scheduler.rs index 4cd9cb5..b97bba0 100644 --- a/crates/doot-lang/src/planner/scheduler.rs +++ b/crates/doot-lang/src/planner/scheduler.rs @@ -11,6 +11,7 @@ pub struct Scheduler { impl Scheduler { /// Creates an empty scheduler. + #[tracing::instrument(level = "trace")] pub fn new() -> Self { Self { graph: DependencyGraph::new(), @@ -18,6 +19,7 @@ impl Scheduler { } /// Creates a scheduler from evaluation results. + #[tracing::instrument(skip_all)] pub fn from_eval_result(result: &EvalResult) -> Self { let mut scheduler = Self::new(); @@ -79,11 +81,13 @@ impl Scheduler { } /// Returns task IDs in execution order. + #[tracing::instrument(level = "trace", skip(self))] pub fn get_execution_order(&self) -> Result, String> { self.graph.topological_sort() } /// Returns tasks grouped into parallel batches. + #[tracing::instrument(level = "trace", skip(self))] pub fn get_parallel_batches(&self) -> Result>, String> { self.graph.get_parallel_batches() } @@ -134,6 +138,7 @@ pub struct DotfileValidation { /// - Overlapping directories (both dirs, one target is ancestor) with same settings → Warning /// - Overlapping directories with different settings → OK, add dependency /// - Directory + file inside → OK, add dependency +#[tracing::instrument(skip_all)] pub fn validate_dotfile_targets( dotfiles: &[DotfileConfig], source_dir: &Path, diff --git a/crates/doot-lang/src/type_checker.rs b/crates/doot-lang/src/type_checker.rs index 2a75af3..9d3dd85 100644 --- a/crates/doot-lang/src/type_checker.rs +++ b/crates/doot-lang/src/type_checker.rs @@ -93,6 +93,7 @@ pub struct TypeChecker { impl TypeChecker { /// Creates a new type checker with built-in types. + #[tracing::instrument(level = "trace")] pub fn new() -> Self { Self { env: TypeEnv::new(), @@ -101,6 +102,7 @@ impl TypeChecker { } /// Type checks a program, returning errors if any. + #[tracing::instrument(level = "trace", skip_all)] pub fn check(&mut self, program: &Program) -> Result<(), Vec> { for stmt in &program.statements { self.check_statement(stmt); @@ -113,6 +115,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all)] fn check_statement(&mut self, stmt: &Spanned) { match &stmt.node { Statement::VarDecl(decl) => { @@ -307,6 +310,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all)] fn infer_expr(&mut self, expr: &Expr, span: &std::ops::Range) -> Type { match expr { Expr::Literal(lit) => match lit { @@ -630,10 +634,7 @@ impl TypeChecker { Type::Function(param_types, Box::new(return_ty)) } - Expr::Await(expr) => { - - self.infer_expr(expr, span) - } + Expr::Await(expr) => self.infer_expr(expr, span), Expr::Path(left, right) => { self.infer_expr(left, span); @@ -654,6 +655,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all, fields(name))] fn infer_builtin_call( &mut self, name: &str, @@ -761,6 +763,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all, fields(method))] fn infer_list_method( &mut self, method: &str, @@ -778,6 +781,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all, fields(method))] fn infer_str_method( &mut self, method: &str, @@ -793,6 +797,7 @@ impl TypeChecker { } } + #[tracing::instrument(level = "trace", skip_all)] fn resolve_type(&self, ty: &TypeAnnotation) -> Type { match ty { TypeAnnotation::Simple(name) => match name.as_str() {