concinnity_dev/command/
explain.rs1use crate::command::{provenance, resolve_world_path};
8
9pub fn explain(name: &str, json_path: Option<&str>) -> std::io::Result<()> {
12 let json_path = resolve_world_path(json_path)?;
13 let content = std::fs::read_to_string(&json_path)?;
14
15 let loaded =
16 concinnity_cook::prepare_world(&content, concinnity_cook::paths::assets_dir().as_deref())
17 .map_err(|errs| concinnity_cook::check::report_validation_errors(&errs))?;
18
19 let Some(asset) = loaded.assets.iter().find(|a| a.name == name) else {
20 let mut close: Vec<&str> = loaded
21 .assets
22 .iter()
23 .map(|a| a.name.as_str())
24 .filter(|n| n.contains(name))
25 .take(5)
26 .collect();
27 close.sort_unstable();
28 let hint = if close.is_empty() {
29 String::new()
30 } else {
31 format!("; close matches: {}", close.join(", "))
32 };
33 return Err(std::io::Error::new(
34 std::io::ErrorKind::NotFound,
35 format!("no asset named '{}' in the expanded world{}", name, hint),
36 ));
37 };
38
39 let line = serde_json::json!({
40 "name": asset.name,
41 "type": asset.asset_type,
42 "args": asset.args,
43 });
44
45 println!("// {}", provenance(&loaded, &asset.name));
46 println!("{}", serde_json::to_string(&line)?);
47 Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 fn write_world(content: &str) -> (tempfile::TempDir, String) {
55 let dir = tempfile::tempdir().unwrap();
56 let path = dir.path().join("world.jsonl");
57 std::fs::write(&path, content).unwrap();
58 (dir, path.to_string_lossy().into_owned())
59 }
60
61 #[test]
62 fn explain_prints_a_known_asset() {
63 let (_dir, path) =
64 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
65 explain("gfx", Some(&path)).unwrap();
66 }
67
68 #[test]
69 fn explain_of_an_unknown_name_offers_close_matches() {
70 let (_dir, path) =
71 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
72 let err = explain("gf", Some(&path)).unwrap_err();
73 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
74 let msg = err.to_string();
75 assert!(msg.contains("close matches"), "got: {msg}");
76 assert!(msg.contains("gfx"), "got: {msg}");
77 }
78
79 #[test]
80 fn explain_of_an_unknown_name_without_matches_has_no_hint() {
81 let (_dir, path) =
82 write_world("{\"name\":\"gfx\",\"type\":\"GraphicsConfig\",\"args\":{}}\n");
83 let err = explain("zzz_nothing", Some(&path)).unwrap_err();
84 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
85 assert!(!err.to_string().contains("close matches"), "got: {err}");
86 }
87
88 #[test]
89 fn explain_surfaces_validation_failures() {
90 let (_dir, path) =
91 write_world("{\"name\":\"odd\",\"type\":\"NotARealAssetType\",\"args\":{}}\n");
92 assert!(explain("odd", Some(&path)).is_err());
93 }
94}