perforce-cli
A type-safe Rust builder library for spawning Perforce (p4) commands.
Instead of concatenating raw argument strings, you construct commands with a fluent builder API. Mutually exclusive option groups and command forms are encoded in the type system, so invalid combinations are rejected at compile time rather than failing at runtime.
Features
- Compile-time option isolation — mutually exclusive flags are modeled as type-state markers. Once a mode is selected, incompatible options simply do not exist on the resulting type.
- Fluent builders — every option has a consuming builder (
x(true)) and a&mut selfsetter (set_x(true)), paired with getters (get_x()). - Generic spawn/output traits — run any command asynchronously with
spawnor to completion withoutput, through theParameterizedSpawntrait and its extension traits. - Multi-version support — select your target Helix Core server version with a single Cargo feature; options introduced or removed in specific releases are gated automatically.
- Zero dependencies.
Requirements
- A
p4executable available onPATH(or a custom path passed toP4Cli::new). - Rust 1.85+ (edition 2024).
Usage
[]
= "0.1.0-alpha.3"
Quick start
use OsStr;
use ParameterizedOutput;
use P4Cli;
Global options (the g-opts accepted by every p4 command) are configured on
GlobalOpts:
use GlobalOpts;
use P4Cli;
let global_opts = new
.port
.client
.user;
let p4 = new;
Type-state: incompatible modes cannot be combined
sync demonstrates compile-time mode isolation. -f/-k/-r force a full
sync, while -s (safe check) and -p (populate) are mutually exclusive with
them:
use OsStr;
use ;
use P4Cli;
let p4 = default;
// `p4 sync -f -q`
let mut force_sync = p4.sync.force.quiet_mode;
force_sync.spawn_with?;
// `p4 sync -s`
let mut safe_sync = p4.sync.enable_safe_check;
safe_sync.spawn_with?;
// `p4 sync -p //depot/project/...`
let mut populate = p4.sync.populate_client_workspace;
populate.spawn_with?;
Calling enable_safe_check() after force(true) does not compile — the
method is not defined for the force mode type, so the invalid command can never
be built.
Commands without file arguments
Some commands take no positional arguments. Use the spawn / output methods
from SpawnExt / OutputExt instead of spawn_with:
use OutputExt;
use P4Cli;
let p4 = default;
// `p4 admin stop`
let output = p4.admin.stop.output?;
Spawning traits
Every command implements ParameterizedSpawn<I> for the input tuple shape(s) I
it accepts; convenience methods are provided by blanket impls:
| Trait | Arguments | Input tuple I |
|---|---|---|
SpawnExt / OutputExt |
none | () |
SpawnExt1 / OutputExt1 |
one | (T1,) |
SpawnExt2 / OutputExt2 … SpawnExt8 / OutputExt8 |
many | (T1, …, TN) |
ParameterizedSpawn / ParameterizedOutput |
raw input | spawn_with(input) / output_with(input) |
spawn/spawn_withreturn astd::process::Childwith stdout and stderr piped.output/output_withwait for the child and return astd::process::Output.
Import the trait matching the call you make; or bring everything into scope with the prelude:
use *;
Targeting a Helix Core version
The crate carries per-version knowledge of every command's options. Enable exactly one version feature for the oldest server version you need to support; options that did not yet exist (or have been removed) are gated out of the API.
[]
# Defaults to the newest supported release.
= "0.1.0-alpha.3"
# Or pin an older server line.
= { = "0.1.0-alpha.3", = false, = ["v2022_2"] }
Supported versions: v2014_1, v2014_2, v2015_1, v2015_2, v2016_1,
v2016_2, v2017_1, v2017_2, v2018_1, v2018_2, v2019_1, v2019_2,
v2020_1, v2020_2, v2021_1, v2021_2, v2022_1, v2022_2, v2023_1,
v2023_2, v2024_1, v2024_2, v2025_1, v2025_2, v2026_1 (default).
Supported commands
add, admin, aliases, annotate, archive, attribute, changes,
describe, edit, filelog, print, sync, where.
More commands are being added.
Examples
See examples/print.rs, which covers capturing output,
chaining builders, and spawning a streaming child process.
cargo run --example print
License
Licensed under the Mozilla Public License, Version 2.0 (LICENSE).