bread-capture: one command for every app, flags for a single one
Plain `bread-capture` with no flags now captures every known app's every view in one run — each binary resolved by its own bare name via $PATH, same as invoking it directly by name would (so an installed bread ecosystem needs nothing but `bread-capture` to regenerate every screenshot). Previously --app-path was required, so there was no way to run more than one app per invocation. --app <name> restricts to a single app (resolved via $PATH, no path needed); --app-path still works alone too, inferring which app by its file stem exactly as before. --view <name> further restricts to one view — apps without a matching view are silently skipped rather than treated as an error, since view names naturally don't overlap across apps in a multi-app run, but an unmatched --view in a single-app run (or one that matches nothing across every selected app) is still a real error.
This commit is contained in:
parent
94feaa6f9b
commit
7ec232b86d
1 changed files with 88 additions and 39 deletions
|
|
@ -2,10 +2,14 @@
|
||||||
//!
|
//!
|
||||||
//! Drives each target app's `--screenshot <view> --output <path>` mode (see
|
//! Drives each target app's `--screenshot <view> --output <path>` mode (see
|
||||||
//! `bread-screenshots` for what that mode does inside the app) and reports
|
//! `bread-screenshots` for what that mode does inside the app) and reports
|
||||||
//! pass/fail per view. One app per invocation, selected by `--app-name`
|
//! pass/fail per view/app. Plain `bread-capture` with no flags captures
|
||||||
//! (defaults to `--app-path`'s file stem, so `--app-path
|
//! every known app's every view in one run — each app's binary is resolved
|
||||||
//! ./target/release/breadbox` needs no separate `--app-name`) — the view
|
//! by its own bare name via `$PATH`, same as running it directly by name
|
||||||
//! list for each app is looked up from [`TARGETS`] below. Flat output
|
//! would. `--app <name>` restricts to one app; `--app-path <path>`
|
||||||
|
//! overrides where its binary is found (and, without `--app`, also selects
|
||||||
|
//! which app by its file stem — so `--app-path ./target/release/breadbox`
|
||||||
|
//! alone still works); `--view <name>` further restricts to one view. The
|
||||||
|
//! view list for each app is looked up from [`TARGETS`] below. Flat output
|
||||||
//! directory for now — no versioned `screenshots/vX.Y.Z/latest` structure
|
//! directory for now — no versioned `screenshots/vX.Y.Z/latest` structure
|
||||||
//! or manifest file yet, since that's still not earning its complexity over
|
//! or manifest file yet, since that's still not earning its complexity over
|
||||||
//! a handful of apps.
|
//! a handful of apps.
|
||||||
|
|
@ -115,14 +119,26 @@ const TARGETS: &[(&str, &[(&str, &str)])] = &[
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Path to the target app's binary (resolved via $PATH if not a path).
|
/// Restrict to one app (see `TARGETS` for known names). Omit to capture
|
||||||
|
/// every known app's every view in one run.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
app_path: String,
|
app: Option<String>,
|
||||||
|
|
||||||
/// Which app's view list to use (see `TARGETS`). Defaults to
|
/// Path to that app's binary (resolved via $PATH if not a path).
|
||||||
/// `--app-path`'s file stem, e.g. `./target/release/breadbox` -> `breadbox`.
|
/// Without `--app`, this also selects *which* app by its file stem
|
||||||
|
/// (e.g. `./target/release/breadbox` -> `breadbox`) — so a single-app
|
||||||
|
/// run never needs both flags. Ignored (with a warning) if given
|
||||||
|
/// together with a multi-app run (no `--app`, and the path isn't
|
||||||
|
/// resolvable to exactly one app).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
app_name: Option<String>,
|
app_path: Option<String>,
|
||||||
|
|
||||||
|
/// Restrict to one view within the selected app(s) (see each app's
|
||||||
|
/// entry in `TARGETS` for known view names). Apps that don't have a
|
||||||
|
/// view by this name are skipped, not treated as an error, since a
|
||||||
|
/// multi-app run's view names naturally don't all overlap.
|
||||||
|
#[arg(long)]
|
||||||
|
view: Option<String>,
|
||||||
|
|
||||||
/// Directory to write captured PNGs into.
|
/// Directory to write captured PNGs into.
|
||||||
#[arg(long, default_value = "./screenshots")]
|
#[arg(long, default_value = "./screenshots")]
|
||||||
|
|
@ -143,21 +159,49 @@ struct Cli {
|
||||||
isolate_height: u32,
|
isolate_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<ExitCode> {
|
fn known_app_names() -> String {
|
||||||
let cli = Cli::parse();
|
TARGETS.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
let app_name = cli.app_name.clone().unwrap_or_else(|| {
|
/// (app_name, binary_path, views) per selected app.
|
||||||
PathBuf::from(&cli.app_path)
|
type SelectedTarget = (&'static str, String, &'static [(&'static str, &'static str)]);
|
||||||
|
|
||||||
|
/// Resolves which `TARGETS` entries this run covers, and the binary path
|
||||||
|
/// to use for each.
|
||||||
|
fn selected_targets(cli: &Cli) -> Result<Vec<SelectedTarget>> {
|
||||||
|
if let Some(app) = &cli.app {
|
||||||
|
let Some((name, views)) = TARGETS.iter().find(|(n, _)| n == app) else {
|
||||||
|
bail!("no known view list for app '{app}' (known: {})", known_app_names());
|
||||||
|
};
|
||||||
|
let path = cli.app_path.clone().unwrap_or_else(|| name.to_string());
|
||||||
|
return Ok(vec![(name, path, views)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = &cli.app_path {
|
||||||
|
let stem = PathBuf::from(path)
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.map(|s| s.to_string_lossy().into_owned())
|
.map(|s| s.to_string_lossy().into_owned())
|
||||||
.unwrap_or_else(|| cli.app_path.clone())
|
.unwrap_or_else(|| path.clone());
|
||||||
});
|
let Some((name, views)) = TARGETS.iter().find(|(n, _)| *n == stem) else {
|
||||||
let Some((_, views)) = TARGETS.iter().find(|(name, _)| *name == app_name) else {
|
bail!("no known view list for app '{stem}' (known: {})", known_app_names());
|
||||||
bail!(
|
};
|
||||||
"no known view list for app '{app_name}' (known: {})",
|
return Ok(vec![(name, path.clone(), views)]);
|
||||||
TARGETS.iter().map(|(n, _)| *n).collect::<Vec<_>>().join(", ")
|
}
|
||||||
);
|
|
||||||
};
|
// No --app / --app-path at all: every known app, resolved by its own
|
||||||
|
// bare name via $PATH.
|
||||||
|
Ok(TARGETS.iter().map(|(name, views)| (*name, name.to_string(), *views)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<ExitCode> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let targets = selected_targets(&cli)?;
|
||||||
|
|
||||||
|
if let Some(view) = &cli.view {
|
||||||
|
if !targets.iter().any(|(_, _, views)| views.iter().any(|(v, _)| v == view)) {
|
||||||
|
bail!("view '{view}' doesn't match any selected app's views");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bound, not dropped-and-discarded: `_isolation`'s teardown (kill the
|
// Bound, not dropped-and-discarded: `_isolation`'s teardown (kill the
|
||||||
// compositor, remove its socket/config) must run via Drop regardless of
|
// compositor, remove its socket/config) must run via Drop regardless of
|
||||||
|
|
@ -174,24 +218,29 @@ fn main() -> Result<ExitCode> {
|
||||||
let height_str = cli.isolate_height.to_string();
|
let height_str = cli.isolate_height.to_string();
|
||||||
|
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
for (view, filename) in *views {
|
for (app_name, app_path, views) in &targets {
|
||||||
let out_path = cli.out_dir.join(filename);
|
for (view, filename) in *views {
|
||||||
let out_str = out_path.to_string_lossy();
|
if cli.view.as_deref().is_some_and(|v| v != *view) {
|
||||||
let result = bread_utils::proc::run(
|
continue;
|
||||||
&cli.app_path,
|
}
|
||||||
&[
|
let out_path = cli.out_dir.join(filename);
|
||||||
"--screenshot", view,
|
let out_str = out_path.to_string_lossy();
|
||||||
"--output", &out_str,
|
let result = bread_utils::proc::run(
|
||||||
"--width", &width_str,
|
app_path,
|
||||||
"--height", &height_str,
|
&[
|
||||||
],
|
"--screenshot", view,
|
||||||
CAPTURE_TIMEOUT,
|
"--output", &out_str,
|
||||||
);
|
"--width", &width_str,
|
||||||
if result.success {
|
"--height", &height_str,
|
||||||
println!("ok {app_name}/{view} -> {}", out_path.display());
|
],
|
||||||
} else {
|
CAPTURE_TIMEOUT,
|
||||||
failed = true;
|
);
|
||||||
println!("FAIL {app_name}/{view}: {}", result.stderr.trim());
|
if result.success {
|
||||||
|
println!("ok {app_name}/{view} -> {}", out_path.display());
|
||||||
|
} else {
|
||||||
|
failed = true;
|
||||||
|
println!("FAIL {app_name}/{view}: {}", result.stderr.trim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue