mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 13:16:50 -06:00
egui037: reconcile current main and finalize published dependency pins
This commit is contained in:
+30
-9
@@ -49,25 +49,35 @@ struct Entry {
|
||||
kind: String,
|
||||
target: String,
|
||||
pending: bool,
|
||||
/// Fixed params as a compact JSON object (`params` in keytips.json), empty for none.
|
||||
params: String,
|
||||
excel: String,
|
||||
note: String,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
/// The probe key: a command and the params its keytip passes.
|
||||
fn probe_key(&self) -> String {
|
||||
if self.params.is_empty() || self.params == "{}" { self.target.clone() } else { format!("{} {}", self.target, self.params) }
|
||||
}
|
||||
}
|
||||
|
||||
/// 0 = ran and journaled, 1 = ran without a journal step, 2 = errored (params
|
||||
/// missing, disabled, or environment-dependent).
|
||||
/// missing, disabled, or environment-dependent). Keyed by [`Entry::probe_key`].
|
||||
type Probe = std::collections::HashMap<String, u8>;
|
||||
|
||||
#[cfg(feature = "engine")]
|
||||
fn probe_live_commands(ids: &[String]) -> Probe {
|
||||
fn probe_live_commands(calls: &[(String, String)]) -> Probe {
|
||||
use gridcraft_engine::Session;
|
||||
let mut map = Probe::new();
|
||||
for id in ids {
|
||||
for (id, params) in calls {
|
||||
if ENV_SENSITIVE.contains(&id.as_str()) || UI_DIALOG_COMMANDS.iter().any(|(c, _)| c == id) {
|
||||
continue;
|
||||
}
|
||||
let params = if params.is_empty() { serde_json::json!({}) } else { serde_json::from_str(params).unwrap_or_default() };
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let status = match s.execute(id, serde_json::json!({})) {
|
||||
let status = match s.execute(id, params.clone()) {
|
||||
Ok(_) => {
|
||||
if s.journal.iter().any(|(jid, _)| jid == id) {
|
||||
0
|
||||
@@ -77,7 +87,8 @@ fn probe_live_commands(ids: &[String]) -> Probe {
|
||||
}
|
||||
Err(_) => 2,
|
||||
};
|
||||
map.insert(id.clone(), status);
|
||||
let key = if params.as_object().is_some_and(|o| !o.is_empty()) { format!("{id} {params}") } else { id.clone() };
|
||||
map.insert(key, status);
|
||||
}
|
||||
map
|
||||
}
|
||||
@@ -137,6 +148,13 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
if pending && kind != "command" {
|
||||
return Err(format!("{at}: `pending` is only valid with kind `command`"));
|
||||
}
|
||||
let params = match e.get("params") {
|
||||
None => String::new(),
|
||||
Some(v) if v.is_object() && matches!(kind.as_str(), "command" | "dialog") && !pending => {
|
||||
serde_json::to_string(v).map_err(|e| format!("{at}: params: {e}"))?
|
||||
}
|
||||
Some(_) => return Err(format!("{at}: `params` must be a JSON object, on a non-pending command or dialog")),
|
||||
};
|
||||
match kind.as_str() {
|
||||
"tab" => {
|
||||
if target.is_empty() || !TABS.contains(&target.as_str()) {
|
||||
@@ -168,6 +186,7 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
kind,
|
||||
target,
|
||||
pending,
|
||||
params,
|
||||
excel: e.get("excel").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
||||
note: e.get("note").and_then(|v| v.as_str()).unwrap_or("").to_string(),
|
||||
});
|
||||
@@ -175,10 +194,10 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
// JSON order is authoritative and curated: the first entry for a tab is its canonical
|
||||
// badge (legacy aliases come later), and groups stay together. Do not sort.
|
||||
|
||||
let live_ids: Vec<String> = entries
|
||||
let live_calls: Vec<(String, String)> = entries
|
||||
.iter()
|
||||
.filter(|e| e.kind == "command" && !e.pending)
|
||||
.map(|e| e.target.clone())
|
||||
.map(|e| (e.target.clone(), e.params.clone()))
|
||||
.collect::<std::collections::BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
@@ -192,7 +211,7 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
e.target.clone()
|
||||
})
|
||||
.collect();
|
||||
let mut probe = probe_live_commands(&live_ids);
|
||||
let mut probe = probe_live_commands(&live_calls);
|
||||
for id in landed {
|
||||
let mut s = gridcraft_engine::Session::new();
|
||||
s.new_workbook();
|
||||
@@ -224,7 +243,9 @@ fn render_table(entries: &[Entry]) -> String {
|
||||
let value = match e.kind.as_str() {
|
||||
"tab" => format!("Entry::Tab({:?})", e.target),
|
||||
"command" if e.pending => format!("Entry::Finish(Action::Pending({:?}))", e.target),
|
||||
"command" if !e.params.is_empty() => format!("Entry::Finish(Action::CommandWith({:?}, {:?}))", e.target, e.params),
|
||||
"command" => format!("Entry::Finish(Action::Command({:?}))", e.target),
|
||||
"dialog" if !e.params.is_empty() => format!("Entry::Finish(Action::DialogWith({:?}, {:?}))", e.target, e.params),
|
||||
"dialog" => format!("Entry::Finish(Action::Dialog({:?}))", e.target),
|
||||
"control" => format!("Entry::Finish(Action::Control({:?}))", e.target),
|
||||
"continue" => "Entry::Continue".to_string(),
|
||||
@@ -285,7 +306,7 @@ fn render_tests(entries: &[Entry], probe: &Probe) -> String {
|
||||
seq = e.seq,
|
||||
)
|
||||
}
|
||||
"command" => match probe.get(&e.target).copied().unwrap_or(2) {
|
||||
"command" => match probe.get(&e.probe_key()).copied().unwrap_or(2) {
|
||||
0 => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert!(h.state().session.journal.iter().any(|(id, _)| id == {id:?}), \"{seq} must journal {id}\");",
|
||||
seq = e.seq,
|
||||
|
||||
+24
-3
@@ -2,6 +2,9 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
struct Counts {
|
||||
files: usize,
|
||||
@@ -10,7 +13,24 @@ struct Counts {
|
||||
proptests: usize,
|
||||
}
|
||||
|
||||
fn walk(dir: &Path, c: &mut Counts) {
|
||||
fn walk(dir: &Path, c: &mut Counts, seen: &mut std::collections::HashSet<u64>, budget: &mut usize) {
|
||||
if *budget == 0 {
|
||||
// Hard stop for pathological trees: two directory symlinks pointing at each other
|
||||
// would otherwise be walked exponentially.
|
||||
return;
|
||||
}
|
||||
// Directory identities (dev+inode on unix) make a symlink to a visited directory a
|
||||
// no-op, not another exponential branch of the scan.
|
||||
#[cfg(unix)]
|
||||
let id = std::fs::metadata(dir).ok().and_then(|m| m.dev().checked_mul(1 << 60).and_then(|d| d.checked_add(m.ino())));
|
||||
#[cfg(not(unix))]
|
||||
let id: Option<u64> = None;
|
||||
if let Some(id) = id
|
||||
&& !seen.insert(id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*budget -= 1;
|
||||
let Ok(rd) = std::fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
@@ -18,10 +38,11 @@ fn walk(dir: &Path, c: &mut Counts) {
|
||||
let p = e.path();
|
||||
let name = e.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
// A symlinked directory is descended only if its identity is new (checked above).
|
||||
if p.is_dir() {
|
||||
// Nested fuzz workspaces and build output are not part of the crate.
|
||||
if name != "target" && name != "fuzz" && !name.starts_with('.') {
|
||||
walk(&p, c);
|
||||
walk(&p, c, seen, budget);
|
||||
}
|
||||
} else if name.ends_with(".rs")
|
||||
&& let Ok(s) = std::fs::read_to_string(&p)
|
||||
@@ -71,7 +92,7 @@ pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
|
||||
continue;
|
||||
};
|
||||
let mut c = Counts::default();
|
||||
walk(&dir, &mut c);
|
||||
walk(&dir, &mut c, &mut std::collections::HashSet::new(), &mut 10_000);
|
||||
let ex = if exact { exact_tests(&name) } else { None };
|
||||
rows.push((name, c, ex));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user