Non-interactive commands: apply a profile and list the outputs as JSON (#8) master
Closes #3. ## What the change gives A user changes the outputs with no interface: ```sh dynamonix apply desk dynamonix list --json ``` The `apply` command reads a profile, examines the layout, and sends the plan. The `list` command with `--json` writes the outputs as one JSON object for a script. The program therefore works in a niri key binding, in a script, and in a hotplug rule. ## The apply command `crates/dynamonix/src/apply.rs` holds the command. The pure part is `decide`, a total function from a profile and a snapshot to one of four decisions: - No monitor of the profile is connected. - The layout has a problem that the program must not send. - The compositor already has the layout. - The program can send the plan. The shell around it only executes the decision. The layout comes from `Profile::to_layout`, the examination from `check` and `is_safe`, and the plan from `diff`. Those are the functions of the interface, so the command sends the same plan that the interface sends for the same profile. A warning does not stop the plan, as in the interface. An error stops it. The exit status tells a script what occurred: 0 when the compositor has the layout of the profile, 2 when the program or the compositor does not accept the layout, and 1 for an other error. ## The list command `crates/dynamonix/src/list.rs` holds the two forms of the list. The text form moves there from `main.rs` without a change. The JSON form writes one object: the name of each output gives the key, and the state that the compositor reports gives the value. The command writes nothing else, so a pipe into a JSON tool works. The JSON form does not ask the compositor for its version, because it does not write the version. ## Checks `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --locked -- -D warnings`, and `cargo test --workspace --locked` all pass. The suite holds 188 tests, 15 of them new. The release build passes. The new tests use the `FakeCompositor`; they need no true compositor and no filesystem. The whole change adds no package to the build. The three exit statuses were exercised against the compositor of the program with `--demo`. Reviewed-on: https://git.devtechnica.com/sreedev/dynamonix/pulls/8