use std::{io::IsTerminal, path::Path};
pub mod commands;
pub use heddle_cli_args as cli_args;
pub use heddle_cli_args::*;
pub use heddle_cli_contract::cli::help;
pub use heddle_cli_render::cli::{progress_render, render, style, tips};
use repo::{Config, Repository};
use crate::config::UserConfig;
pub fn is_tty() -> bool {
std::io::stdout().is_terminal()
}
pub fn is_interactive_tty() -> bool {
std::io::stdin().is_terminal()
&& std::io::stdout().is_terminal()
&& std::io::stderr().is_terminal()
}
pub fn execution_context_from_cli(cli: &Cli) -> anyhow::Result<verbs::ExecutionContext> {
let cwd = std::env::current_dir()?;
let start = cli.repo.as_ref().unwrap_or(&cwd).to_path_buf();
let repo = cli.open_repo()?;
let config = UserConfig::load_default()?;
execution_context_from_cli_parts(cli, &start, Some(repo), &config)
}
pub(crate) fn execution_context_from_cli_parts(
cli: &Cli,
start: &Path,
repo: Option<Repository>,
config: &UserConfig,
) -> anyhow::Result<verbs::ExecutionContext> {
let verbosity = if cli.quiet {
verbs::Verbosity::Quiet
} else if cli.verbose > 0 {
verbs::Verbosity::Verbose
} else {
verbs::Verbosity::Normal
};
let fsmonitor_mode = config
.worktree_status_options(repo.as_ref().map(Repository::config))
.fsmonitor
.mode;
let mut builder = verbs::ExecutionContext::builder()
.start_path(start.to_path_buf())
.principal_fallback(
config
.principal_pair()
.map(|(name, email)| (name.to_string(), email.to_string())),
)
.fsmonitor_mode(fsmonitor_mode)
.verbosity(verbosity)
.progress(std::sync::Arc::new(verbs::NoopProgress))
.warnings(std::sync::Arc::new(verbs::NoopWarnings));
if let Some(repo) = repo {
builder = builder.repo(repo);
}
if let Some(op_id) = crate::operation_id::resolve_operation_id(cli)? {
builder = builder.op_id(op_id.to_string());
}
Ok(builder.build())
}
pub fn user_config_or_exit() -> &'static UserConfig {
Cli::user_config_or_exit()
}
pub fn load_user_config_or_exit() -> UserConfig {
user_config_or_exit().clone()
}
pub fn output_is_compact(cli: &Cli) -> bool {
matches!(cli.output_mode(), Some(OutputMode::JsonCompact))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JsonOutputMode {
Text,
Json,
Jsonl,
}
pub fn json_output_mode_for_kind(
cli: &Cli,
config: Option<&Config>,
json_kind: &str,
) -> JsonOutputMode {
match json_kind {
"jsonl" => {
if matches!(
cli.output_mode(),
Some(OutputMode::Json | OutputMode::JsonCompact)
) {
JsonOutputMode::Jsonl
} else {
JsonOutputMode::Text
}
}
"json" | "json_or_jsonl" => {
if should_output_json(cli, config) {
JsonOutputMode::Json
} else {
JsonOutputMode::Text
}
}
"none" => JsonOutputMode::Text,
_ => JsonOutputMode::Text,
}
}
pub fn worktree_status_options(config: Option<&Config>) -> repo::WorktreeStatusOptions {
user_config_or_exit().worktree_status_options(config)
}
#[cfg(test)]
mod tests {
use clap::Parser;
use super::*;
#[test]
fn jsonl_commands_require_explicit_json_output() {
let auto = Cli::try_parse_from(["heddle", "watch"]).expect("watch should parse");
assert_eq!(
json_output_mode_for_kind(&auto, None, "jsonl"),
JsonOutputMode::Text
);
let json = Cli::try_parse_from(["heddle", "--output", "json", "watch"])
.expect("watch --output json should parse");
assert_eq!(
json_output_mode_for_kind(&json, None, "jsonl"),
JsonOutputMode::Jsonl
);
let text = Cli::try_parse_from(["heddle", "--output", "text", "watch"])
.expect("watch --output text should parse");
assert_eq!(
json_output_mode_for_kind(&text, None, "jsonl"),
JsonOutputMode::Text
);
}
#[test]
fn canonical_context_adapter_maps_cli_state_for_an_injected_repository() {
let temp = tempfile::tempdir().expect("temp repository");
Repository::init_default(temp.path()).expect("init repository");
let repo = Repository::open(temp.path()).expect("open repository");
let path = temp.path().to_string_lossy().into_owned();
let cli = Cli::try_parse_from(["heddle", "-v", "-C", &path, "status"])
.expect("status should parse");
let config = UserConfig::default();
let ctx = execution_context_from_cli_parts(&cli, temp.path(), Some(repo), &config)
.expect("build execution context");
assert_eq!(ctx.start_path(), Some(temp.path()));
assert_eq!(ctx.require_repo().expect("repo").root(), temp.path());
assert_eq!(ctx.verbosity(), verbs::Verbosity::Verbose);
assert!(ctx.op_id().is_none());
assert_eq!(
ctx.fsmonitor_mode(),
config
.worktree_status_options(Some(ctx.require_repo().expect("repo").config()))
.fsmonitor
.mode
);
}
}