Skip to content
Merged
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
5 changes: 3 additions & 2 deletions crates/term-transcript-cli/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn test_config() -> (TestConfig<StdShell>, TempDir) {
// Switch off logging if `RUST_LOG` is set in the surrounding env
.with_env("RUST_LOG", "off")
.with_current_dir(temp_dir.path())
.with_cargo_path()
.with_cargo_path_for("term-transcript")
.with_additional_path(rainbow_dir)
.with_io_timeout(Duration::from_secs(2));
let config = TestConfig::new(shell_options).with_match_kind(MatchKind::Precise);
Expand All @@ -58,7 +58,8 @@ fn scrolled_template() -> Template {
fn help_example() {
use term_transcript::PtyCommand;

let shell_options = ShellOptions::new(PtyCommand::default()).with_cargo_path();
let shell_options =
ShellOptions::new(PtyCommand::default()).with_cargo_path_for("term-transcript");
TestConfig::new(shell_options).test(svg_snapshot("help"), ["term-transcript --help"]);
}

Expand Down
6 changes: 5 additions & 1 deletion crates/term-transcript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)

- Bump minimum supported Rust version to 1.86.

### Fixed

- Rework `ShellOptions::with_cargo_path()` to work with custom target directories.

## 0.5.0-beta.1 - 2026-02-04

### Added
Expand Down Expand Up @@ -104,7 +108,7 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
As an example, this can be used to import fonts using `@import` or `@font-face`.
- Add a fallback error message to the default template if HTML-in-SVG embedding
is not supported.
- Add [FAQ](../FAQ.md) with some tips and troubleshooting advice.
- Add a FAQ with some tips and troubleshooting advice.
- Allow hiding `UserInput`s during transcript rendering by calling the `hide()` method.
Hidden inputs are supported by the default and pure SVG templates.

Expand Down
68 changes: 60 additions & 8 deletions crates/term-transcript/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,25 +263,77 @@ impl<Cmd: ConfigureCommand> ShellOptions<Cmd> {
path
}

/// Adds paths to cargo binaries (including examples) to the `PATH` env variable
/// for the shell described by these options.
/// This allows to call them by the corresponding filename, without specifying a path
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", ret))]
fn legacy_cargo_path(binary_name: &str) -> Option<PathBuf> {
let target_path = Self::target_path();
let binary_path = target_path.join(format!("{binary_name}{}", env::consts::EXE_SUFFIX));
let exists = binary_path.try_exists();

#[cfg(feature = "tracing")]
tracing::debug!(?binary_path, ?exists, "checked binary path");
exists.ok()?.then_some(binary_path)
}

fn panic_on_missing_cargo_path(binary_name: &str) -> ! {
let binaries: Vec<_> = env::vars_os()
.filter_map(|(name, _)| {
let name = name.into_string().ok()?;
Some(name.strip_prefix("CARGO_BIN_EXE_")?.to_owned())
})
.collect();
if binaries.is_empty() {
panic!(
"`CARGO_BIN_EXE_{binary_name}` env variable is unset, and {binary_name} is not in the default cargo target dir.\n\
help: If this is run in a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_` vars (requires Rust 1.94+)"
);
} else {
panic!(
"`{binary_name}` does not look like a valid cargo binary in the workspace.\n\
help: Available binaries: {binaries:?}"
);
}
}

/// Adds paths to a cargo binary to the `PATH` env variable for the shell described by these options.
/// This allows to call the binary by the corresponding filename, without specifying a path
/// or doing complex preparations (e.g., calling `cargo install`).
///
/// # Limitations
///
/// - The caller must be a unit or integration test; the method will work improperly otherwise.
/// - Does not work in Rust 1.91, 1.92, 1.93 with a non-default `build.build-dir`.
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self)))]
#[must_use]
pub fn with_cargo_path(mut self) -> Self {
let target_path = Self::target_path();
self.path_additions.push(target_path.join("examples"));
self.path_additions.push(target_path);
#[allow(clippy::missing_panics_doc)] // should never be triggered
pub fn with_cargo_path_for(mut self, binary_name: &str) -> Self {
let env_var_name = format!("CARGO_BIN_EXE_{binary_name}");
let binary_path = env::var_os(&env_var_name).map(PathBuf::from);

#[cfg(feature = "tracing")]
tracing::debug!(?binary_path, "got Rust 1.94+ path to binary");

let binary_path = binary_path
.or_else(|| Self::legacy_cargo_path(binary_name))
.unwrap_or_else(|| Self::panic_on_missing_cargo_path(binary_name));

#[cfg(feature = "tracing")]
tracing::debug!(?binary_path, "got path to binary");

let parent_path = binary_path
.parent()
.expect("invalid binary path")
.to_owned();
// The check is inefficient, but we shouldn't have many additional paths.
if !self.path_additions.contains(&parent_path) {
self.path_additions.push(parent_path);
}

self
}

/// Adds a specified path to the `PATH` env variable for the shell described by these options.
/// This method can be called multiple times to add multiple paths and is composable
/// with [`Self::with_cargo_path()`].
/// with [`Self::with_cargo_path_for()`].
#[must_use]
pub fn with_additional_path(mut self, path: impl Into<PathBuf>) -> Self {
let path = path.into();
Expand Down
2 changes: 1 addition & 1 deletion crates/term-transcript/src/shell/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl ShellOptions<StdShell> {
/// Creates an alias for the binary at `path_to_bin`, which should be an absolute path.
/// This allows to call the binary using this alias without complex preparations (such as
/// installing it globally via `cargo install`), and is more flexible than
/// [`Self::with_cargo_path()`].
/// [`Self::with_cargo_path_for()`].
///
/// In integration tests, you may use [`env!("CARGO_BIN_EXE_<name>")`] to get a path
/// to binary targets.
Expand Down
3 changes: 2 additions & 1 deletion crates/term-transcript/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
//!
//! // Test configuration that can be shared across tests.
//! fn config() -> TestConfig {
//! let shell_options = ShellOptions::default().with_cargo_path();
//! let shell_options = ShellOptions::default()
//! .with_cargo_path_for("my-command");
//! TestConfig::new(shell_options)
//! .with_match_kind(MatchKind::Precise)
//! .with_output(TestOutputConfig::Verbose)
Expand Down
4 changes: 1 addition & 3 deletions crates/term-transcript/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ fn transcript_with_empty_output(mute_outputs: &[bool], pure_svg: bool) -> anyhow
}
});

let mut shell_options = ShellOptions::default()
.with_cargo_path()
.with_io_timeout(Duration::from_millis(200));
let mut shell_options = ShellOptions::default().with_io_timeout(Duration::from_millis(200));
let transcript = Transcript::from_inputs(&mut shell_options, inputs)?;
assert_tracing_for_transcript_from_inputs(&tracing_storage.lock());

Expand Down
Loading