Readme
This repository contains a Rust implementation of SSOF (Shell Safe Options Format), along with a small command-line tool for converting between SSOF and JSON.
The workspace currently includes two crates:
ssof : a library for parsing SSOF into JSON, serializing JSON into normalized SSOF, and applying SSOF patches to existing JSON values.
ssof-cli : a command-line tool for conversion checks, quick manual testing, and shell-based workflows.
The format specification lives in shell-safe-options-format.md .
Purpose
SSOF is designed to express hierarchical configuration in a single shell-safe string while still supporting:
object and array construction
explicit path entry, backtracking, and backtrack-only navigation
boolean flags
array append and remove operations
incremental updates to existing configuration
This repository focuses on turning the specification into a testable, embeddable Rust implementation.
The ssof Library
ssof uses serde_json:: Value as its core data model and currently exposes three main entry points:
parse_str ( & str ) -> Result < Value, Error>
apply_str ( & mut Value, & str ) -> Result < ( ) , Error>
to_string ( & Value) -> Result < String , Error>
These cover three related use cases:
parse a full SSOF string into JSON
apply an SSOF patch to an existing JSON value
serialize JSON into normalized SSOF
Example: SSOF to JSON
use ssof:: parse_str;
let value = parse_str ( " system:,+debug,db.nodes:,10.0.0.1,10.0.0.2" ) ? ;
The resulting JSON is equivalent to:
{
" system" : {
" debug" : true ,
" db" : {
" nodes" : [ " 10.0.0.1" , " 10.0.0.2" ]
}
}
}
Example: Applying an Incremental Patch
use serde_json:: json;
use ssof:: apply_str;
let mut config = json! ( {
" system" : {
" tags" : [ " prod" , " test" ]
}
} ) ;
apply_str ( & mut config, " system:,tags-=test,tags+=stable" ) ? ;
Example: Self-Path Operations
The explicit-current path . can target the current namespace itself.
use ssof:: parse_str;
let root_object = parse_str ( " .:" ) ? ;
let root_array = parse_str ( " .::" ) ? ;
let nested = parse_str ( " items:,.0:,.+=prod,.+=stable" ) ? ;
These values are equivalent to:
{ }
[ ]
{ " items" : [ [ " prod" , " stable" ] ] }
Example: Backtracking Without Implicit null
Backtracking with a prefix : and no remaining body changes only the current path. It does not append a null value.
use ssof:: parse_str;
let navigates_only = parse_str ( " .::,.0:,x=y,:,2" ) ? ;
let explicit_null = parse_str ( " .::,.0:,x=y,:=,2" ) ? ;
These values are equivalent to:
[ { " x" : " y" } , 2 ]
[ { " x" : " y" } , null , 2 ]
Example: JSON to SSOF
use serde_json:: json;
use ssof:: to_string;
let text = to_string ( & json! ( {
" system" : {
" debug" : true ,
" nodes" : [ " 10.0.0.1" , " 10.0.0.2" ]
}
} ) ) ? ;
Serialization produces a normalized SSOF string. The goal is stable round-tripping and predictable output, not preservation of the original handwritten form.
ssof-cli uses a single-command, flag-driven interface that works well in scripts and one-off conversions.
Basic Usage
cargo run - p ssof-cli -- --from ssof --to json --input ' system:,+debug'
cargo run - p ssof-cli -- --from json --to ssof --input ' {"system":{"debug":true}}'
Reading from a File
cargo run - p ssof-cli -- --from ssof --to json --input-file input.ssof --pretty
printf ' {"system":{"debug":true}}\n' | cargo run - p ssof-cli -- --from json --to ssof --input-file -
Applying a Patch to Existing JSON
cargo run - p ssof-cli -- \
--from ssof \
--to json \
--apply \
--base-file config.json \
--input ' system:,db.nodes+=10.0.0.3'
Flags
--from < json| ssof> : input format
--to < json| ssof> : output format
--apply : treat the input as an SSOF patch and apply it to a base JSON value
--input < text> : provide input directly
--input-file < path > : read input from a file; use - to read from stdin
--base < json> : provide base JSON directly; only valid with --apply
--base-file < path > : read base JSON from a file; use - to read from stdin ; only valid with --apply
--pretty : pretty-print JSON output
Input precedence is: --input > --input-file > stdin .
When using --apply , --input-file - and --base-file - cannot be used together because they would both consume the same stdin stream.
Current Coverage
The implementation currently covers the core behavior described by the specification, including:
entry splitting and percent-decoding
key path parsing, explicit current-namespace addressing, and self-path operations
distinction between object keys and array indexes
container typing, path backtracking, and backtrack-only entries
execution of = , : , : : , + flag, - flag, += , and -= operations
normalized serialization through serde_json:: Value
The repository also includes unit tests for minimal construction, incremental updates, array indexing, self-path operations, forced values, and representative error cases.
Known Limits
The current implementation follows the existing specification closely, but it also enforces a few explicit limits:
empty strings are not currently serializable
Those are current format and v1 implementation boundaries, not accidental omissions.
Development
Run Tests
cargo test
cargo fmt -- all -- check