report: grow the Prompter port and move display_path out of the ui module
Prompter gains interactive(), select() and text() (with the Validator type), and confirm() now propagates cancellation as Err so flows abort instead of silently taking a default when the user hits Ctrl+C. The terminal prompter implements the full port; the port also re-exports the path display helper, which is pure presentation formatting used by events and messages rather than terminal code.
This commit is contained in:
+67
-6
@@ -14,13 +14,20 @@
|
||||
//! implementations used by headless runs and tests. [`Prompter`] is
|
||||
//! deliberately blocking: an implementation may round-trip each question to
|
||||
//! a remote user, as long as it eventually answers (or takes the default).
|
||||
//! Asking can fail (the user cancels, the connection drops); flows
|
||||
//! propagate the error, which aborts them.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::context::LineSink;
|
||||
use crate::logfmt::Classifier;
|
||||
|
||||
/// Answer validator of [`Prompter::text`]: accepts the answer, or explains
|
||||
/// why it is rejected (the implementation re-asks with the explanation).
|
||||
pub type Validator = dyn Fn(&str) -> Result<(), String>;
|
||||
|
||||
/// Identity of the build whose events follow, as announced through
|
||||
/// [`BuildView::target`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -94,18 +101,51 @@ pub trait BuildView: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Answerer of the questions a core flow may ask mid-run (confirmations
|
||||
/// now, selections and free-text input as more flows migrate).
|
||||
/// Answerer of the questions a core flow may ask mid-run.
|
||||
///
|
||||
/// Questions are blocking on purpose: a terminal implementation waits for
|
||||
/// key presses, and a builder-server implementation may forward the question
|
||||
/// to a web client and await the answer on a channel. Implementations that
|
||||
/// cannot ask anyone answer with the question's default.
|
||||
pub trait Prompter: Send + Sync {
|
||||
/// Whether this prompter can interact with a user at all. Flows with an
|
||||
/// interactive and a headless path use this to pick one: a headless run
|
||||
/// takes the defaults or fails with the list of missing answers instead
|
||||
/// of asking question by question.
|
||||
fn interactive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Ask a yes/no question. `default` is the answer to take when no user
|
||||
/// can be reached or the question is cancelled.
|
||||
fn confirm(&self, _question: &str, default: bool) -> bool {
|
||||
default
|
||||
/// can be reached; `Err` means the question was cancelled (Ctrl+C,
|
||||
/// dropped connection) and the flow should abort.
|
||||
fn confirm(&self, _question: &str, default: bool) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(default)
|
||||
}
|
||||
|
||||
/// Ask a one-line selection among `options`, with `default`
|
||||
/// preselected. Selector implementations may also accept typed
|
||||
/// arbitrary text; callers validate the answer and re-ask through the
|
||||
/// same method when they must reject one. `Err` cancels the flow.
|
||||
fn select(
|
||||
&self,
|
||||
_label: &str,
|
||||
_options: &[String],
|
||||
default: &str,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
Ok(default.to_string())
|
||||
}
|
||||
|
||||
/// Ask a free-text answer. `validate` is applied by the implementation
|
||||
/// so invalid input re-asks at the source (mid-input for a terminal,
|
||||
/// round-trip for a server). `Err` cancels the flow.
|
||||
fn text(
|
||||
&self,
|
||||
_label: &str,
|
||||
default: &str,
|
||||
_validate: Option<&Validator>,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
Ok(default.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,3 +156,24 @@ pub struct Quiet;
|
||||
|
||||
impl BuildView for Quiet {}
|
||||
impl Prompter for Quiet {}
|
||||
|
||||
/// Render a path for terminal display: relative to the current working
|
||||
/// directory when the target lives inside it or directly next to it
|
||||
/// (`../name`, the usual layout of build artifacts), absolute otherwise.
|
||||
///
|
||||
/// Pure formatting shared by the flows that mention paths in their events
|
||||
/// and messages; a remote frontend reproduces it (or not) on its side.
|
||||
pub fn display_path(path: &Path) -> String {
|
||||
let Ok(cwd) = std::env::current_dir() else {
|
||||
return path.display().to_string();
|
||||
};
|
||||
if let Ok(rel) = path.strip_prefix(&cwd) {
|
||||
return rel.display().to_string();
|
||||
}
|
||||
if let Some(parent) = cwd.parent()
|
||||
&& let Ok(rel) = path.strip_prefix(parent)
|
||||
{
|
||||
return format!("../{}", rel.display());
|
||||
}
|
||||
path.display().to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user