prototext CLI designStatus: implemented App: prototext Implemented in: 2026-03-10
A standalone Rust binary (prototext) is needed that exercises prototext-core
end-to-end. Before implementation can start, the CLI surface must be precisely
specified.
.pb descriptors only).--self-test), lax mode, --force, --debug.| Short | Long | Description |
|---|---|---|
-d | --decode | Decode: treat input as binary protobuf, emit text |
-e | --encode | Encode: treat input as textual prototext, emit binary |
-d and -e are mandatory and mutually exclusive: exactly one must be
given. Omitting both or supplying both is an error.
| Short | Long | Description |
|---|---|---|
-D PATH | --descriptor PATH | Path to a compiled .pb descriptor file |
-t NAME | --type NAME | Fully-qualified root message type name (e.g. pkg.MyMessage) |
Rules:
-D and -t must be both given or both absent. One without the other
is an error.-t is given without -D, the binary's embedded descriptor is used
as the schema source. This covers all google.protobuf.* types (notably
google.protobuf.FileDescriptorProto) without requiring a file on disk.Summary:
-D | -t | Schema used |
|---|---|---|
| absent | absent | schemaless |
| absent | given | embedded descriptor.pb |
| given | absent | error |
| given | given | descriptor at -D path |
| Short | Long | Description |
|---|---|---|
--annotations / --no-annotations | Emit inline comments with wire type and field number (default: on). Required for lossless round-trip. | |
-o PATH | --output PATH | Write output to PATH (single input only; exclusive with -O) |
-O DIR | --output-root DIR | Root directory for output files (batch mode; exclusive with -o and -i) |
| Short | Long | Description |
|---|---|---|
-I DIR | --input-root DIR | Root directory: positional paths and glob expansion are relative to DIR |
-i | --in-place | Rewrite each input file in place (exclusive with -O) |
| Short | Long | Description |
|---|---|---|
-q | --quiet | Suppress warnings on stderr (errors are always printed) |
-h | --help | Print help and exit |
prototext -d|-e [OPTIONS] [PATH...]
Each positional PATH may be:
*, **, ?,
[...]). A glob that matches zero files is an error.When -I DIR is given:
When -I is absent, all of the above are relative to cwd.
No positional arguments — read from stdin. -i and -O are errors when
reading from stdin (no path to write back to).
-o and -O are mutually exclusive.-i and -O are mutually exclusive.-I DIR and -O DIR resolving to the same directory is an error (use -i
instead).-I given is an error.Output goes to stdout unless -o PATH is given, in which case output goes to
PATH. -O with a single input is an error.
Batch mode requires either -i or -O. Neither with multiple inputs is an
error.
Each input file has a relative path:
-I DIR is given: the file's path relative to DIR.-I is absent: the path as given on the CLI (or as expanded from the
glob/directory).The output path for each file is:
-O DIR: DIR/<relative-path>, creating parent directories as needed.-i: the file's original absolute path. Each file is read fully into
memory before its output is written (sponge semantics — safe even when input
and output are the same path).Extension is never changed in batch mode.
In batch mode, a failure on one file does not abort processing of subsequent files. All errors are collected and printed to stderr at the end. Exit code is 1 if any file failed, 0 if all succeeded.
Dynamic completion using clap + clap_complete with the unstable-dynamic
feature enabled — the same model used by Cargo today
(CARGO_COMPLETE=bash cargo).
Activation:
# bash (with workaround for path-completion bugs in clap_complete)
source <(PROTOTEXT_COMPLETE=bash prototext | sed \
-e '/^\s*) )$/a\ compopt -o filenames 2>/dev/null' \
-e 's|words\[COMP_CWORD\]="$2"|local _cur="${COMP_LINE:0:$COMP_POINT}"; _cur="${_cur##* }"; words[COMP_CWORD]="$_cur"|')
Completion behaviour:
-I DIR when -I has already
been typed; otherwise relative to cwd. Implemented via a custom
ArgValueCompleter (complete_input_paths) that reads -I from the
already-parsed partial args.-D PATH completes .pb files only (custom complete_pb_files).-t NAME completes known message names: from the descriptor at -D if -D
has already been typed, or from the embedded descriptor otherwise
(complete_type_names).Bash word-split fix for dotted type names. Bash uses . as a
COMP_WORDBREAKS character, so google.protobuf.F is split and the binary
only receives F as the current token. The generated completion script is
patched with two sed substitutions at load time (see activation command
above):
compopt -o filenames — tells readline candidates are filesystem paths,
suppressing bash's tendency to strip the dotted prefix.COMP_LINE/COMP_POINT — reconstructs the full token
(e.g. google.protobuf.F) so the prefix filter in complete_type_names
works correctly.unstable-dynamic feature.include_bytes!.# Decode foo.pb to stdout (schemaless)
prototext -d foo.pb
# Decode foo.pb with schema (annotations on by default)
prototext -d -D knife.pb -t SwissArmyKnife foo.pb
# Decode foo.pb with schema, suppress annotations (protoc-compatible output)
prototext -d -D knife.pb -t SwissArmyKnife --no-annotations foo.pb
# Decode a FileDescriptorProto using the embedded descriptor
prototext -d -t google.protobuf.FileDescriptorProto foo.pb
# Encode textproto back to binary
prototext -e foo.txtpb -o foo.pb
# Batch decode all .pb files under src/, write results under out/
prototext -d -D knife.pb -t SwissArmyKnife -I src/ -O out/ '**/*.pb'
# In-place batch encode (read fully, then overwrite)
prototext -e -i '**/*.txtpb'
# Read from stdin, decode schemalessly
cat foo.pb | prototext -d
# Enable bash completion
source <(PROTOTEXT_COMPLETE=bash prototext | sed \
-e '/^\s*) )$/a\ compopt -o filenames 2>/dev/null' \
-e 's|words\[COMP_CWORD\]="$2"|local _cur="${COMP_LINE:0:$COMP_POINT}"; _cur="${_cur##* }"; words[COMP_CWORD]="$_cur"|')
- as explicit stdin placeholder in the positional list
would enable scripting convenience (e.g. prototext -d - foo.pb to mix
stdin with files). Disallow -i when - is in the list. Deferred.docs/annotation-format.md — annotation grammarfixtures/index.toml — shared fixture set