Add explicit read-only mixed source8 CLI checking
This commit is contained in:
+66
-11
@@ -170,6 +170,47 @@ fn disk_error(report: &mut Report, error: SourceFileError) {
|
||||
report.fail(3, code, error.to_string());
|
||||
}
|
||||
|
||||
/// Validate captured mixed source8 bytes without creating an editable candidate.
|
||||
pub fn check_mixed(entry: &Path) -> Report {
|
||||
let mut report = Report::empty(entry, "check-mixed");
|
||||
let snapshot = match source_files::load_mixed_source(entry, LIMITS) {
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(error) => {
|
||||
disk_error(&mut report, error);
|
||||
return report;
|
||||
}
|
||||
};
|
||||
let diagnostics = if snapshot.diagnostics().is_empty() {
|
||||
match language::check_mixed_bundle(snapshot.bundle(), LIMITS) {
|
||||
Ok(checked) => checked.warnings().to_vec(),
|
||||
Err(errors) => errors,
|
||||
}
|
||||
} else {
|
||||
snapshot.diagnostics().to_vec()
|
||||
};
|
||||
if diagnostics
|
||||
.iter()
|
||||
.any(|d| d.diagnostic.severity == Severity::Error)
|
||||
{
|
||||
report.status = "invalid".into();
|
||||
report.exit_code = if diagnostics.iter().any(|d| {
|
||||
matches!(
|
||||
d.diagnostic.code.as_str(),
|
||||
"pending_save" | "missing_include" | "invalid_path"
|
||||
)
|
||||
}) {
|
||||
3
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
report.diagnostics = diagnostics
|
||||
.into_iter()
|
||||
.map(|d| located(d, "input"))
|
||||
.collect();
|
||||
report
|
||||
}
|
||||
|
||||
/// A candidate owns its original expectations and is consumed by saving.
|
||||
/// Callers cannot replace its source/model/expectations after preparation.
|
||||
pub struct Prepared {
|
||||
@@ -406,14 +447,19 @@ fn unified_diff(path: &str, before: &str, after: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Read-only mixed checking cannot acquire ordinary edit/write authority.
|
||||
pub enum Dispatch {
|
||||
Editable { operation: Operation, write: bool },
|
||||
CheckMixed,
|
||||
}
|
||||
|
||||
pub struct Arguments {
|
||||
pub entry: PathBuf,
|
||||
pub operation: Operation,
|
||||
pub write: bool,
|
||||
pub dispatch: Dispatch,
|
||||
pub json: bool,
|
||||
}
|
||||
|
||||
pub const HELP: &str = "arcadia check ROOT [--json]\narcadia lint ROOT [--json]\narcadia stabilize ROOT [--write] [--json]\narcadia rename-alias ROOT OLD NEW [--write] [--json]\n\nROOT is an explicit .arcadia entry file; includes resolve relative to it.\nMutation commands preview by default. --write applies this invocation's candidate.\nMissing UUIDs in a preview are provisional until written. No canonical formatting.\nExit: 0 valid/success (engineering warnings allowed), 1 invalid source or semantic\ncommand rejection, 2 usage, 3 disk/path/conflict/pending-journal/output failure.\nNo implicit journal recovery. Limits: 64 MiB, 256 files, include depth32/edges1024,\n100000 graph objects, 250000 records, 500000 ordered references.\n";
|
||||
pub const HELP: &str = "arcadia check ROOT [--json]\narcadia lint ROOT [--json]\narcadia check-mixed ROOT [--json]\narcadia stabilize ROOT [--write] [--json]\narcadia rename-alias ROOT OLD NEW [--write] [--json]\n\nROOT is an explicit .arcadia entry file; includes resolve relative to it.\ncheck-mixed requires source8 and explicit UUIDs; it never migrates, stabilizes or writes.\nMutation commands preview by default. --write applies this invocation's candidate.\nMissing UUIDs in a preview are provisional until written. No canonical formatting.\nExit: 0 valid/success (engineering warnings allowed), 1 invalid source or semantic\ncommand rejection, 2 usage, 3 disk/path/conflict/pending-journal/output failure.\nNo implicit journal recovery. Limits: 64 MiB, 256 files, include depth32/edges1024,\n100000 graph objects, 250000 records, 500000 ordered references.\n";
|
||||
|
||||
pub fn arguments(args: &[std::ffi::OsString]) -> Result<Arguments, String> {
|
||||
let mut positional = Vec::new();
|
||||
@@ -433,19 +479,28 @@ pub fn arguments(args: &[std::ffi::OsString]) -> Result<Arguments, String> {
|
||||
}
|
||||
}
|
||||
let command = positional.first().and_then(|s| s.to_str()).unwrap_or("");
|
||||
let operation = match (command, positional.len()) {
|
||||
("check" | "lint", 2) if !write => Operation::Check,
|
||||
("stabilize", 2) => Operation::Stabilize,
|
||||
("rename-alias", 4) => Operation::RenameAlias {
|
||||
old: positional[2].to_str().ok_or("Alias must be UTF-8")?.into(),
|
||||
new: positional[3].to_str().ok_or("Alias must be UTF-8")?.into(),
|
||||
let dispatch = match (command, positional.len()) {
|
||||
("check-mixed", 2) if !write => Dispatch::CheckMixed,
|
||||
("check" | "lint", 2) if !write => Dispatch::Editable {
|
||||
operation: Operation::Check,
|
||||
write,
|
||||
},
|
||||
("stabilize", 2) => Dispatch::Editable {
|
||||
operation: Operation::Stabilize,
|
||||
write,
|
||||
},
|
||||
("rename-alias", 4) => Dispatch::Editable {
|
||||
operation: Operation::RenameAlias {
|
||||
old: positional[2].to_str().ok_or("Alias must be UTF-8")?.into(),
|
||||
new: positional[3].to_str().ok_or("Alias must be UTF-8")?.into(),
|
||||
},
|
||||
write,
|
||||
},
|
||||
_ => return Err(HELP.into()),
|
||||
};
|
||||
Ok(Arguments {
|
||||
entry: PathBuf::from(positional[1]),
|
||||
operation,
|
||||
write,
|
||||
dispatch,
|
||||
json,
|
||||
})
|
||||
}
|
||||
|
||||
+30
-19
@@ -1,4 +1,4 @@
|
||||
use oa_cli::{Report, arguments, prepare};
|
||||
use oa_cli::{Dispatch, Report, arguments, check_mixed, prepare};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
@@ -26,13 +26,21 @@ fn human_report(report: &Report, show_diff: bool) -> io::Result<()> {
|
||||
d.severity, d.code, d.coordinate_space, d.message
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
err,
|
||||
"{}: {} file changes; {} identities to stabilize",
|
||||
report.status,
|
||||
report.changes.len(),
|
||||
report.identities_to_stabilize
|
||||
)?;
|
||||
if report.operation == "check-mixed" {
|
||||
writeln!(
|
||||
err,
|
||||
"{}: mixed source8 check; no files changed",
|
||||
report.status
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
err,
|
||||
"{}: {} file changes; {} identities to stabilize",
|
||||
report.status,
|
||||
report.changes.len(),
|
||||
report.identities_to_stabilize
|
||||
)?;
|
||||
}
|
||||
if let Some(migration) = &report.migration {
|
||||
writeln!(
|
||||
err,
|
||||
@@ -67,19 +75,22 @@ fn run() -> io::Result<u8> {
|
||||
let mut displayed = false;
|
||||
let report = match arguments(&args) {
|
||||
Err(error) => Report::failure(Path::new(""), "usage", 2, "usage", error),
|
||||
Ok(args) => match prepare(&args.entry, args.operation) {
|
||||
Err(report) => *report,
|
||||
Ok(prepared) => {
|
||||
if args.write {
|
||||
if !args.json {
|
||||
human_report(prepared.report(), true)?;
|
||||
displayed = true;
|
||||
Ok(args) => match args.dispatch {
|
||||
Dispatch::CheckMixed => check_mixed(&args.entry),
|
||||
Dispatch::Editable { operation, write } => match prepare(&args.entry, operation) {
|
||||
Err(report) => *report,
|
||||
Ok(prepared) => {
|
||||
if write {
|
||||
if !args.json {
|
||||
human_report(prepared.report(), true)?;
|
||||
displayed = true;
|
||||
}
|
||||
prepared.write()
|
||||
} else {
|
||||
prepared.into_report()
|
||||
}
|
||||
prepared.write()
|
||||
} else {
|
||||
prepared.into_report()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
if json {
|
||||
|
||||
Reference in New Issue
Block a user