- 1
//! `vak office`: the Office engine for scripts and headless machines - 2
//! (docs/design/72-openxml-documents.md, P5). Each verb only parses its - 3
//! arguments and prints the worker's JSON answer: reading, applying, - 4
//! comparing and verifying run in the same broker worker tasks, over the - 5
//! same op schema, as the Agent's tools and the Review screen, so a file is - 6
//! never parsed in this process (invariants 14 and 39). - 7
- 8
use std::io::Read as _; - 9
use std::path::{Path, PathBuf}; - 10
- 11
use crate::cli::OfficeAction; - 12
- 13
pub(crate) async fn run_office(action: OfficeAction) -> i32 { - 14
let worker = match std::env::current_exe() { - 15
Ok(executable) => executable, - 16
Err(error) => return fail(&format!("tool broker unavailable: {error}")), - 17
}; - 18
let answer = match action { - 19
OfficeAction::Read { - 20
file, - 21
from, - 22
at, - 23
structure, - 24
facts, - 25
} => read(&worker, &file, from, at, structure, facts).await, - 26
OfficeAction::Apply { - 27
file, - 28
ops, - 29
base_digest, - 30
out, - 31
agent, - 32
} => { - 33
apply( - 34
&worker, - 35
file.as_deref(), - 36
&ops, - 37
base_digest.as_deref(), - 38
&out, - 39
&agent, - 40
) - 41
.await - 42
} - 43
OfficeAction::Diff { before, after } => diff(&worker, &before, &after).await, - 44
OfficeAction::Verify { file } => return verify(&worker, &file).await, - 45
}; - 46
match answer { - 47
Ok(value) => print(&value), - 48
Err(error) => fail(&error), - 49
} - 50
} - 51
- 52
async fn read( - 53
worker: &Path, - 54
file: &Path, - 55
from: usize, - 56
at: Option<String>, - 57
structure: bool, - 58
facts: bool, - 59
) -> Result<serde_json::Value, String> { - 60
use vak_tools::broker::OfficeView; - 61
let view = match (structure, facts, at) { - 62
(true, _, _) => OfficeView::Structure, - 63
(_, true, _) => OfficeView::Facts, - 64
(_, _, Some(anchor)) => OfficeView::At { anchor }, - 65
_ => OfficeView::Content { from }, - 66
}; - 67
vak_tools::broker::office_project(worker, &absolute(file)?, view).await - 68
} - 69
- 70
async fn apply( - 71
worker: &Path, - 72
file: Option<&Path>, - 73
ops: &str, - 74
base_digest: Option<&str>, - 75
out: &Path, - 76
agent: &str, - 77
) -> Result<serde_json::Value, String> { - 78
let text = if ops == "-" { - 79
let mut text = String::new(); - 80
std::io::stdin() - 81
.read_to_string(&mut text) - 82
.map_err(|error| format!("could not read ops from stdin: {error}"))?; - 83
text - 84
} else { - 85
std::fs::read_to_string(ops).map_err(|error| format!("could not read {ops}: {error}"))? - 86
}; - 87
let ops: Vec<vak_ooxml::edit::OfficeOp> = serde_json::from_str(&text).map_err(|error| { - 88
format!("ops are not valid: {error}. Pass a JSON array of ops, each an object with an \"op\" name and only that op's fields") - 89
})?; - 90
let origin = match (file, base_digest) { - 91
(Some(file), Some(base_digest)) => vak_tools::broker::OfficeOrigin::File { - 92
path: absolute(file)?, - 93
base_digest: base_digest.to_string(), - 94
}, - 95
(None, None) => vak_tools::broker::OfficeOrigin::Blank, - 96
(Some(_), None) => { - 97
return Err( - 98
"--base-digest is required with a file: pass the sha256 `vak office read` printed for it" - 99
.into(), - 100
); - 101
} - 102
(None, Some(_)) => { - 103
return Err( - 104
"--base-digest names the content of a file; give the file, or leave both out to create --out from scratch" - 105
.into(), - 106
); - 107
} - 108
}; - 109
// A new file from the blank or a template is written clean; anything - 110
// else is an edit of the file, tracked (docs/design/72, R7). - 111
let new_file = file.is_none_or(|file| { - 112
file.extension() - 113
.and_then(|extension| extension.to_str()) - 114
.and_then(vak_ooxml::Format::from_extension) - 115
.is_some_and(|format| format.kind == vak_ooxml::FormatKind::Template) - 116
}); - 117
let lineage = vak_tools::broker::OfficeLineage { - 118
origin, - 119
ops, - 120
author: vak_tools::office_apply::tracked_change_author(agent), - 121
new_file, - 122
}; - 123
vak_tools::broker::office_apply_to(worker, &lineage, &absolute(out)?).await - 124
} - 125
- 126
async fn diff(worker: &Path, before: &Path, after: &Path) -> Result<serde_json::Value, String> { - 127
vak_tools::broker::office_review(worker, Some(&absolute(before)?), &absolute(after)?, None) - 128
.await - 129
} - 130
- 131
/// Exits 0 when the file passes the Open XML verifier, 1 when it fails. - 132
async fn verify(worker: &Path, file: &Path) -> i32 { - 133
let (root, name) = match absolute(file).and_then(|path| { - 134
let name = path - 135
.file_name() - 136
.and_then(|name| name.to_str()) - 137
.map(str::to_string) - 138
.ok_or_else(|| format!("{} has no file name", path.display()))?; - 139
let root = path - 140
.parent() - 141
.map(Path::to_path_buf) - 142
.ok_or_else(|| format!("{} has no directory", path.display()))?; - 143
Ok((root, name)) - 144
}) { - 145
Ok(parts) => parts, - 146
Err(error) => return fail(&error), - 147
}; - 148
let checks = [vak_sandbox::TargetCheckPlan { - 149
verifier: "format.openxml".into(), - 150
path: name, - 151
}]; - 152
let results = vak_tools::broker::verify_targets(worker, &root, &checks).await; - 153
let passed = results.iter().all(|result| result.status == "passed"); - 154
let code = print(&serde_json::json!({ "passed": passed, "checks": results })); - 155
if code != 0 { - 156
code - 157
} else if passed { - 158
0 - 159
} else { - 160
1 - 161
} - 162
} - 163
- 164
fn absolute(path: &Path) -> Result<PathBuf, String> { - 165
std::path::absolute(path).map_err(|error| format!("{}: {error}", path.display())) - 166
} - 167
- 168
fn print(value: &serde_json::Value) -> i32 { - 169
match serde_json::to_string_pretty(value) { - 170
Ok(text) => { - 171
println!("{text}"); - 172
0 - 173
} - 174
Err(error) => fail(&error.to_string()), - 175
} - 176
} - 177
- 178
fn fail(message: &str) -> i32 { - 179
eprintln!("{}", serde_json::json!({ "error": message })); - 180
2 - 181
} - 182
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.