Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 67 additions & 67 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "dev-scope"
name = "dx-scope"
version = "0.0.0-dev"
edition = "2024"
default-run = "scope"
Expand Down
17 changes: 16 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@ use anyhow::Result;
use vergen::EmitBuilder;

pub fn main() -> Result<()> {
EmitBuilder::builder().all_build().all_git().emit()?;
let mut builder = EmitBuilder::builder();

// Build info always works
builder.all_build();

// Git info only available when building from git repo
// This allows the crate to be built from crates.io where .git doesn't exist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good comment. Says why not what. Double plus good.

if std::path::Path::new(".git").exists() {
builder.all_git();
} else {
// Provide default values when not in a git repo
println!("cargo:rustc-env=VERGEN_GIT_DESCRIBE=unknown");
println!("cargo:rustc-env=VERGEN_GIT_SHA=unknown");
println!("cargo:rustc-env=VERGEN_GIT_COMMIT_DATE=unknown");
}

builder.emit()?;
Ok(())
}
2 changes: 1 addition & 1 deletion src/bin/scope-intercept.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use dev_scope::prelude::*;
use dx_scope::prelude::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 questions

  • Why?
  • What does dx refer to here?

use human_panic::setup_panic;
use std::env;
use std::sync::Arc;
Expand Down
4 changes: 2 additions & 2 deletions src/bin/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use anyhow::Result;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use colored::Colorize;
use dev_scope::prelude::*;
use dev_scope::report_stdout;
use dx_scope::prelude::*;
use dx_scope::report_stdout;
use human_panic::setup_panic;
use lazy_static::lazy_static;
use regex::Regex;
Expand Down
95 changes: 95 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! CLI-specific implementations for interactive usage.
//!
//! This module provides implementations of the library traits that are
//! suitable for command-line interface usage, including:
//!
//! - Interactive prompts using the `inquire` crate
//! - Visual progress reporting using `tracing-indicatif`
//!
//! # When to Use
//!
//! Use the implementations in this module when building CLI applications
//! that need interactive user prompts. For library usage, automated
//! environments, or testing, use the implementations in [`crate::internal`].
//!
//! # Examples
//!
//! ## Interactive CLI Application
//!
//! ```rust
//! use dx_scope::InquireInteraction;
//! use dx_scope::internal::prompts::UserInteraction;
//!
//! let interaction = InquireInteraction;
//!
//! // This will show an interactive prompt in the terminal
//! // In non-TTY environments (like this doc test), it returns false
//! let result = interaction.confirm("Apply this fix?", Some("This will modify files"));
//! // result is false in doc test environment (no TTY)
//! ```
//!
//! # TTY Detection
//!
//! `InquireInteraction` automatically detects when stdin is not a TTY
//! (e.g., when running in a pipe or CI environment) and returns `false`
//! instead of crashing. For explicit control in non-interactive environments,
//! use [`AutoApprove`](crate::AutoApprove) or [`DenyAll`](crate::DenyAll).

use crate::internal::prompts::UserInteraction;
use inquire::InquireError;
use tracing::warn;

/// CLI user interaction using the `inquire` crate.
///
/// This implementation provides interactive prompts suitable for terminal usage.
/// It handles TTY detection and gracefully falls back to denial when running
/// in non-interactive environments.
///
/// # Example
///
/// ```rust
/// use dx_scope::InquireInteraction;
/// use dx_scope::internal::prompts::UserInteraction;
///
/// let interaction = InquireInteraction;
/// // In non-TTY environments, confirm() returns false gracefully
/// let confirmed = interaction.confirm("Apply fix?", Some("This will modify files"));
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct InquireInteraction;

impl UserInteraction for InquireInteraction {
fn confirm(&self, prompt: &str, help_text: Option<&str>) -> bool {
tracing_indicatif::suspend_tracing_indicatif(|| {
let base_prompt = inquire::Confirm::new(prompt).with_default(false);
let prompt = match help_text {
Some(text) => base_prompt.with_help_message(text),
None => base_prompt,
};

match prompt.prompt() {
Ok(result) => result,
Err(InquireError::NotTTY) => {
warn!(target: "user", "Prompting user, but input device is not a TTY. Skipping.");
false
}
Err(_) => false,
}
})
}

fn notify(&self, message: &str) {
println!("{}", message);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_inquire_interaction_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<InquireInteraction>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test appears to be testing the compiler's type system.
I don't think we need to do that.

}
}
82 changes: 82 additions & 0 deletions src/internal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Internal abstractions for library-first design.
//!
//! This module contains traits and implementations that allow the scope library
//! to be used both as a CLI tool and as a programmatic library. These abstractions
//! decouple the core logic from specific UI implementations.
//!
//! # Overview
//!
//! The internal module provides:
//! - [`prompts`] - User interaction abstractions (confirmations, notifications)
//! - [`progress`] - Progress reporting abstractions
//!
//! # Choosing an Implementation
//!
//! | Use Case | UserInteraction | ProgressReporter |
//! |----------|-----------------|------------------|
//! | CLI/Interactive | `InquireInteraction` | (use tracing-indicatif) |
//! | CI/Automated | `AutoApprove` | `NoOpProgress` |
//! | Dry-run/Testing | `DenyAll` | `NoOpProgress` |
//! | Custom | Implement trait | Implement trait |
//!
//! # Examples
//!
//! ## Automated Environment (CI)
//!
//! ```rust
//! use dx_scope::internal::prompts::{UserInteraction, AutoApprove};
//! use dx_scope::internal::progress::{ProgressReporter, NoOpProgress};
//!
//! let interaction = AutoApprove;
//! let progress = NoOpProgress;
//!
//! // All prompts will be automatically approved
//! assert!(interaction.confirm("Apply fix?", None));
//!
//! // Progress calls are silent
//! progress.start_group("build", 5);
//! progress.finish_group();
//! ```
//!
//! ## Dry-Run Mode
//!
//! ```rust
//! use dx_scope::internal::prompts::{UserInteraction, DenyAll};
//!
//! let interaction = DenyAll;
//!
//! // All prompts will be denied - no changes made
//! assert!(!interaction.confirm("Apply fix?", None));
//! ```
//!
//! ## Custom Implementation
//!
//! ```rust
//! use dx_scope::internal::prompts::UserInteraction;
//!
//! struct AlwaysAskUser;
//!
//! impl UserInteraction for AlwaysAskUser {
//! fn confirm(&self, prompt: &str, _help: Option<&str>) -> bool {
//! // Custom logic - maybe read from a config file
//! println!("Would prompt: {}", prompt);
//! false
//! }
//!
//! fn notify(&self, message: &str) {
//! println!("[INFO] {}", message);
//! }
//! }
//! ```
//!
//! # Thread Safety
//!
//! All provided implementations are `Send + Sync`, making them safe to use
//! across async tasks and threads.

pub mod progress;
pub mod prompts;

// Re-export commonly used types at the module level
pub use progress::{NoOpProgress, ProgressReporter};
pub use prompts::{AutoApprove, DenyAll, UserInteraction};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of this, we should stop reexporting everything all the time.
This dumps all of these types into the internal namespace.

Loading