mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 16:33:22 -06:00
M2/M8/M9/M4.4: drawing + transform tools, live effects, PDF export/import, file/batch commands; UI restyled to measured Illustrator metrics (Medium Dark, categorized toolbar, hint bar, task bar, Layers)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d0007a604d
commit
6d21d36d16
+39
-190
@@ -63,14 +63,9 @@ pub const INTRA_LAYER_ORDER: &[&[&str]] = &[&["geom", "color"], &["pathops", "ef
|
||||
|
||||
fn intra_layer_allowed(from: &str, to: &str) -> bool {
|
||||
let (from, to) = (short_name(from), short_name(to));
|
||||
INTRA_LAYER_ORDER.iter().any(|chain| {
|
||||
match (
|
||||
chain.iter().position(|n| *n == from),
|
||||
chain.iter().position(|n| *n == to),
|
||||
) {
|
||||
(Some(f), Some(t)) => t < f,
|
||||
_ => false,
|
||||
}
|
||||
INTRA_LAYER_ORDER.iter().any(|chain| match (chain.iter().position(|n| *n == from), chain.iter().position(|n| *n == to)) {
|
||||
(Some(f), Some(t)) => t < f,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,64 +115,30 @@ pub struct Crate {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Violation {
|
||||
Unregistered {
|
||||
krate: String,
|
||||
},
|
||||
Upward {
|
||||
krate: String,
|
||||
dep: String,
|
||||
from: u8,
|
||||
to: u8,
|
||||
kind: DepKind,
|
||||
},
|
||||
StandaloneHasWorkspaceDep {
|
||||
krate: String,
|
||||
dep: String,
|
||||
},
|
||||
TestkitAsNormalDep {
|
||||
krate: String,
|
||||
},
|
||||
UiBelowL6 {
|
||||
krate: String,
|
||||
dep: String,
|
||||
layer: u8,
|
||||
},
|
||||
Unregistered { krate: String },
|
||||
Upward { krate: String, dep: String, from: u8, to: u8, kind: DepKind },
|
||||
StandaloneHasWorkspaceDep { krate: String, dep: String },
|
||||
TestkitAsNormalDep { krate: String },
|
||||
UiBelowL6 { krate: String, dep: String, layer: u8 },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Violation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Violation::Unregistered { krate } => write!(
|
||||
f,
|
||||
"{krate}: unknown workspace crate; register it in xtask/src/layers.rs TABLE (see plan/architecture.md §3)"
|
||||
),
|
||||
Violation::Upward {
|
||||
krate,
|
||||
dep,
|
||||
from,
|
||||
to,
|
||||
kind,
|
||||
} => write!(
|
||||
f,
|
||||
"{krate} (L{from}) -> {dep} (L{to}) [{kind:?}]: may only depend on strictly lower layers"
|
||||
),
|
||||
Violation::Unregistered { krate } => {
|
||||
write!(f, "{krate}: unknown workspace crate; register it in xtask/src/layers.rs TABLE (see plan/architecture.md §3)")
|
||||
}
|
||||
Violation::Upward { krate, dep, from, to, kind } => {
|
||||
write!(f, "{krate} (L{from}) -> {dep} (L{to}) [{kind:?}]: may only depend on strictly lower layers")
|
||||
}
|
||||
Violation::StandaloneHasWorkspaceDep { krate, dep } => {
|
||||
write!(
|
||||
f,
|
||||
"{krate}: standalone crate must not depend on workspace crate {dep}"
|
||||
)
|
||||
write!(f, "{krate}: standalone crate must not depend on workspace crate {dep}")
|
||||
}
|
||||
Violation::TestkitAsNormalDep { krate } => {
|
||||
write!(
|
||||
f,
|
||||
"{krate}: drawcraft-testkit may only be a dev-dependency"
|
||||
)
|
||||
write!(f, "{krate}: drawcraft-testkit may only be a dev-dependency")
|
||||
}
|
||||
Violation::UiBelowL6 { krate, dep, layer } => {
|
||||
write!(
|
||||
f,
|
||||
"{krate} (L{layer}) depends on UI crate `{dep}`; UI toolkits are only allowed in L6+"
|
||||
)
|
||||
write!(f, "{krate} (L{layer}) depends on UI crate `{dep}`; UI toolkits are only allowed in L6+")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,9 +149,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
||||
let mut out = Vec::new();
|
||||
for c in crates {
|
||||
let Some(class) = classify(&c.name) else {
|
||||
out.push(Violation::Unregistered {
|
||||
krate: c.name.clone(),
|
||||
});
|
||||
out.push(Violation::Unregistered { krate: c.name.clone() });
|
||||
continue;
|
||||
};
|
||||
if class == Class::Exempt {
|
||||
@@ -204,10 +163,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
||||
}
|
||||
if d.workspace {
|
||||
if class == Class::Standalone {
|
||||
out.push(Violation::StandaloneHasWorkspaceDep {
|
||||
krate: c.name.clone(),
|
||||
dep: d.name.clone(),
|
||||
});
|
||||
out.push(Violation::StandaloneHasWorkspaceDep { krate: c.name.clone(), dep: d.name.clone() });
|
||||
continue;
|
||||
}
|
||||
match classify(&d.name) {
|
||||
@@ -215,9 +171,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
||||
None => {}
|
||||
Some(Class::Testkit) => {
|
||||
if d.kind != DepKind::Dev {
|
||||
out.push(Violation::TestkitAsNormalDep {
|
||||
krate: c.name.clone(),
|
||||
});
|
||||
out.push(Violation::TestkitAsNormalDep { krate: c.name.clone() });
|
||||
}
|
||||
}
|
||||
Some(dc) => {
|
||||
@@ -234,11 +188,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
||||
}
|
||||
}
|
||||
} else if layer < UI_MIN_LAYER && is_ui_crate(&d.name) {
|
||||
out.push(Violation::UiBelowL6 {
|
||||
krate: c.name.clone(),
|
||||
dep: d.name.clone(),
|
||||
layer,
|
||||
});
|
||||
out.push(Violation::UiBelowL6 { krate: c.name.clone(), dep: d.name.clone(), layer });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,9 +199,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
||||
|
||||
/// Build the model from `cargo metadata --format-version 1 --no-deps`.
|
||||
pub fn from_metadata(meta: &Value) -> Result<Vec<Crate>, String> {
|
||||
let pkgs = meta["packages"]
|
||||
.as_array()
|
||||
.ok_or("metadata: no packages array")?;
|
||||
let pkgs = meta["packages"].as_array().ok_or("metadata: no packages array")?;
|
||||
let members: Vec<&str> = pkgs.iter().filter_map(|p| p["name"].as_str()).collect();
|
||||
let mut out = Vec::new();
|
||||
for p in pkgs {
|
||||
@@ -265,11 +213,7 @@ pub fn from_metadata(meta: &Value) -> Result<Vec<Crate>, String> {
|
||||
_ => DepKind::Normal,
|
||||
};
|
||||
let workspace = members.contains(&dname.as_str()) || d["path"].is_string();
|
||||
deps.push(Dep {
|
||||
name: dname,
|
||||
kind,
|
||||
workspace,
|
||||
});
|
||||
deps.push(Dep { name: dname, kind, workspace });
|
||||
}
|
||||
out.push(Crate { name, deps });
|
||||
}
|
||||
@@ -292,17 +236,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn c(name: &str, deps: &[(&str, DepKind, bool)]) -> Crate {
|
||||
Crate {
|
||||
name: name.into(),
|
||||
deps: deps
|
||||
.iter()
|
||||
.map(|(n, k, w)| Dep {
|
||||
name: (*n).into(),
|
||||
kind: *k,
|
||||
workspace: *w,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
Crate { name: name.into(), deps: deps.iter().map(|(n, k, w)| Dep { name: (*n).into(), kind: *k, workspace: *w }).collect() }
|
||||
}
|
||||
use DepKind::*;
|
||||
|
||||
@@ -311,17 +245,8 @@ mod tests {
|
||||
let g = [
|
||||
c("drawcraft-geom", &[("kurbo", Normal, false)]),
|
||||
c("drawcraft-doc", &[("drawcraft-geom", Normal, true)]),
|
||||
c(
|
||||
"drawcraft-engine",
|
||||
&[
|
||||
("drawcraft-doc", Normal, true),
|
||||
("drawcraft-testkit", Dev, true),
|
||||
],
|
||||
),
|
||||
c(
|
||||
"drawcraft-ui-egui",
|
||||
&[("drawcraft-engine", Normal, true), ("egui", Normal, false)],
|
||||
),
|
||||
c("drawcraft-engine", &[("drawcraft-doc", Normal, true), ("drawcraft-testkit", Dev, true)]),
|
||||
c("drawcraft-ui-egui", &[("drawcraft-engine", Normal, true), ("egui", Normal, false)]),
|
||||
c("drawcraft-cli", &[("drawcraft-ui-egui", Normal, true)]),
|
||||
];
|
||||
assert!(check(&g).is_empty(), "{:?}", check(&g));
|
||||
@@ -335,30 +260,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn sideways_dependency_flagged() {
|
||||
let v = check(&[c("drawcraft-ops", &[("drawcraft-algo", Normal, true)])]);
|
||||
let v = check(&[c("drawcraft-text", &[("drawcraft-pathops", Normal, true)])]);
|
||||
assert!(matches!(v[..], [Violation::Upward { from: 2, to: 2, .. }]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn l0_foundation_chain_allowed_one_way() {
|
||||
assert!(
|
||||
check(&[c(
|
||||
"drawcraft-raster",
|
||||
&[
|
||||
("drawcraft-color", Normal, true),
|
||||
("drawcraft-geom", Normal, true)
|
||||
]
|
||||
)])
|
||||
.is_empty()
|
||||
);
|
||||
assert!(check(&[c("drawcraft-color", &[("drawcraft-geom", Normal, true)])]).is_empty());
|
||||
let v = check(&[c("drawcraft-geom", &[("drawcraft-raster", Normal, true)])]);
|
||||
let v = check(&[c("drawcraft-geom", &[("drawcraft-color", Normal, true)])]);
|
||||
assert!(matches!(v[..], [Violation::Upward { from: 0, to: 0, .. }]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_dev_dependency_ignored() {
|
||||
assert!(check(&[c("drawcraft-psd", &[("drawcraft-psd", Dev, true)])]).is_empty());
|
||||
assert!(check(&[c("drawcraft-doc", &[("drawcraft-doc", Dev, true)])]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -367,103 +282,37 @@ mod tests {
|
||||
assert!(matches!(v[..], [Violation::Upward { kind: Dev, .. }]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_crates_have_no_workspace_deps() {
|
||||
for s in [
|
||||
"drawcraft-psd",
|
||||
"drawcraft-codecs",
|
||||
"drawcraft-adobe-assets",
|
||||
] {
|
||||
let v = check(&[c(s, &[("drawcraft-geom", Normal, true)])]);
|
||||
assert!(
|
||||
matches!(v[..], [Violation::StandaloneHasWorkspaceDep { .. }]),
|
||||
"{s}"
|
||||
);
|
||||
assert!(check(&[c(s, &[("image", Normal, false)])]).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_is_usable_from_higher_layers() {
|
||||
assert!(check(&[c("drawcraft-io", &[("drawcraft-psd", Normal, true)])]).is_empty());
|
||||
let v = check(&[c("drawcraft-geom", &[("drawcraft-codecs", Normal, true)])]);
|
||||
assert!(matches!(v[..], [Violation::Upward { from: 0, to: 0, .. }]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_crates_below_l6_flagged() {
|
||||
for dep in [
|
||||
"egui",
|
||||
"eframe",
|
||||
"winit",
|
||||
"egui_kittest",
|
||||
"rfd",
|
||||
"bevy_ecs",
|
||||
"bevy",
|
||||
] {
|
||||
for dep in ["egui", "eframe", "winit", "egui_kittest", "rfd", "bevy_ecs", "bevy"] {
|
||||
let v = check(&[c("drawcraft-engine", &[(dep, Normal, false)])]);
|
||||
assert!(
|
||||
matches!(v[..], [Violation::UiBelowL6 { layer: 5, .. }]),
|
||||
"{dep}"
|
||||
);
|
||||
assert!(matches!(v[..], [Violation::UiBelowL6 { layer: 5, .. }]), "{dep}");
|
||||
}
|
||||
assert!(
|
||||
check(&[c(
|
||||
"drawcraft-engine",
|
||||
&[("egui_extras_not", Normal, false)]
|
||||
)])
|
||||
.is_empty()
|
||||
);
|
||||
assert!(check(&[c("drawcraft-platform", &[("winit", Normal, false)])]).is_empty());
|
||||
assert!(check(&[c("drawcraft-engine", &[("egui_extras_not", Normal, false)])]).is_empty());
|
||||
assert!(check(&[c("drawcraft-mcp", &[("winit", Normal, false)])]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregistered_crate_is_error() {
|
||||
let v = check(&[c("drawcraft-mystery", &[])]);
|
||||
assert!(
|
||||
matches!(&v[..], [Violation::Unregistered { krate }] if krate == "drawcraft-mystery")
|
||||
);
|
||||
assert!(matches!(&v[..], [Violation::Unregistered { krate }] if krate == "drawcraft-mystery"));
|
||||
assert!(v[0].to_string().contains("register"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn testkit_only_as_dev_dependency() {
|
||||
let v = check(&[c(
|
||||
"drawcraft-raster",
|
||||
&[("drawcraft-testkit", Normal, true)],
|
||||
)]);
|
||||
let v = check(&[c("drawcraft-render", &[("drawcraft-testkit", Normal, true)])]);
|
||||
assert!(matches!(v[..], [Violation::TestkitAsNormalDep { .. }]));
|
||||
assert!(check(&[c("drawcraft-raster", &[("drawcraft-testkit", Dev, true)])]).is_empty());
|
||||
assert!(check(&[c("drawcraft-render", &[("drawcraft-testkit", Dev, true)])]).is_empty());
|
||||
// testkit itself may use anything up to L5 but not L6 crates.
|
||||
assert!(
|
||||
check(&[c(
|
||||
"drawcraft-testkit",
|
||||
&[("drawcraft-engine", Normal, true)]
|
||||
)])
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
!check(&[c(
|
||||
"drawcraft-testkit",
|
||||
&[("drawcraft-ui-egui", Normal, true)]
|
||||
)])
|
||||
.is_empty()
|
||||
);
|
||||
assert!(check(&[c("drawcraft-testkit", &[("drawcraft-engine", Normal, true)])]).is_empty());
|
||||
assert!(!check(&[c("drawcraft-testkit", &[("drawcraft-ui-egui", Normal, true)])]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apps_and_xtask_exempt() {
|
||||
for app in ["drawcraft", "drawcraft-cli", "drawcraft-web", "xtask"] {
|
||||
assert!(
|
||||
check(&[c(
|
||||
app,
|
||||
&[
|
||||
("egui", Normal, false),
|
||||
("drawcraft-ui-egui", Normal, true)
|
||||
]
|
||||
)])
|
||||
.is_empty()
|
||||
);
|
||||
assert!(check(&[c(app, &[("egui", Normal, false), ("drawcraft-ui-egui", Normal, true)])]).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-61
@@ -47,10 +47,7 @@ fn main() -> ExitCode {
|
||||
|
||||
/// Workspace root (parent of the xtask crate).
|
||||
pub fn root() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("xtask has a parent dir")
|
||||
.to_path_buf()
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask has a parent dir").to_path_buf()
|
||||
}
|
||||
|
||||
pub fn cargo() -> Command {
|
||||
@@ -61,26 +58,14 @@ pub fn cargo() -> Command {
|
||||
|
||||
fn run(mut cmd: Command, what: &str) -> Result<(), String> {
|
||||
eprintln!("$ {what}");
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|e| format!("{what}: failed to spawn: {e}"))?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{what}: exited with {status}"))
|
||||
}
|
||||
let status = cmd.status().map_err(|e| format!("{what}: failed to spawn: {e}"))?;
|
||||
if status.success() { Ok(()) } else { Err(format!("{what}: exited with {status}")) }
|
||||
}
|
||||
|
||||
pub fn metadata() -> Result<serde_json::Value, String> {
|
||||
let out = cargo()
|
||||
.args(["metadata", "--format-version", "1", "--no-deps"])
|
||||
.output()
|
||||
.map_err(|e| format!("cargo metadata: {e}"))?;
|
||||
let out = cargo().args(["metadata", "--format-version", "1", "--no-deps"]).output().map_err(|e| format!("cargo metadata: {e}"))?;
|
||||
if !out.status.success() {
|
||||
return Err(format!(
|
||||
"cargo metadata failed:\n{}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
));
|
||||
return Err(format!("cargo metadata failed:\n{}", String::from_utf8_lossy(&out.stderr)));
|
||||
}
|
||||
serde_json::from_slice(&out.stdout).map_err(|e| format!("cargo metadata: bad JSON: {e}"))
|
||||
}
|
||||
@@ -103,12 +88,7 @@ fn cmd_layers() -> Result<(), String> {
|
||||
format!("{}{k}", layers::short_name(&d.name))
|
||||
})
|
||||
.collect();
|
||||
println!(
|
||||
"{:<28} {:<14} {}",
|
||||
c.name,
|
||||
layers::describe(layers::classify(&c.name)),
|
||||
ws.join(", ")
|
||||
);
|
||||
println!("{:<28} {:<14} {}", c.name, layers::describe(layers::classify(&c.name)), ws.join(", "));
|
||||
}
|
||||
let violations = layers::check(&crates);
|
||||
println!();
|
||||
@@ -145,11 +125,7 @@ fn cmd_wasm() -> Result<(), String> {
|
||||
for pkg in &set {
|
||||
let mut c = cargo();
|
||||
c.args(["check", "--target", "wasm32-unknown-unknown", "-p", pkg]);
|
||||
let ok = run(
|
||||
c,
|
||||
&format!("cargo check --target wasm32-unknown-unknown -p {pkg}"),
|
||||
)
|
||||
.is_ok();
|
||||
let ok = run(c, &format!("cargo check --target wasm32-unknown-unknown -p {pkg}")).is_ok();
|
||||
results.push((pkg.clone(), ok));
|
||||
}
|
||||
println!("\nwasm32-unknown-unknown check:");
|
||||
@@ -157,11 +133,7 @@ fn cmd_wasm() -> Result<(), String> {
|
||||
println!(" {:<6} {p}", if *ok { "ok" } else { "FAIL" });
|
||||
}
|
||||
let failed = results.iter().filter(|r| !r.1).count();
|
||||
if failed == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{failed} crate(s) failed the wasm check"))
|
||||
}
|
||||
if failed == 0 { Ok(()) } else { Err(format!("{failed} crate(s) failed the wasm check")) }
|
||||
}
|
||||
|
||||
fn cmd_ci() -> Result<(), String> {
|
||||
@@ -179,14 +151,7 @@ fn cmd_ci() -> Result<(), String> {
|
||||
"clippy",
|
||||
Box::new(|| {
|
||||
let mut c = cargo();
|
||||
c.args([
|
||||
"clippy",
|
||||
"--workspace",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings",
|
||||
]);
|
||||
c.args(["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"]);
|
||||
run(c, "cargo clippy --workspace --all-targets -- -D warnings")
|
||||
}),
|
||||
),
|
||||
@@ -217,11 +182,7 @@ fn cmd_ci() -> Result<(), String> {
|
||||
}
|
||||
done.push(*name);
|
||||
}
|
||||
println!(
|
||||
"\nCI summary: all {} steps passed ({})",
|
||||
done.len(),
|
||||
done.join(", ")
|
||||
);
|
||||
println!("\nCI summary: all {} steps passed ({})", done.len(), done.join(", "));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -256,17 +217,7 @@ Tests that use a corpus skip cleanly when it is absent.
|
||||
tar.arg("-xzf").arg(&tgz).arg("-C").arg(&dest);
|
||||
run(tar, "tar -xzf PngSuite-2017jul19.tgz")?;
|
||||
let _ = std::fs::remove_file(&tgz);
|
||||
let n = std::fs::read_dir(&dest)
|
||||
.map(|d| {
|
||||
d.flatten()
|
||||
.filter(|e| e.path().extension().is_some_and(|x| x == "png"))
|
||||
.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let n = std::fs::read_dir(&dest).map(|d| d.flatten().filter(|e| e.path().extension().is_some_and(|x| x == "png")).count()).unwrap_or(0);
|
||||
println!("PngSuite: {n} PNG files in {}", dest.display());
|
||||
if n == 0 {
|
||||
Err("no PNG files extracted".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
if n == 0 { Err("no PNG files extracted".into()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
+10
-41
@@ -55,19 +55,11 @@ fn count_tests(src: &str) -> (usize, usize) {
|
||||
}
|
||||
|
||||
fn exact_tests(pkg: &str) -> Option<usize> {
|
||||
let out = crate::cargo()
|
||||
.args(["test", "-q", "-p", pkg, "--", "--list"])
|
||||
.output()
|
||||
.ok()?;
|
||||
let out = crate::cargo().args(["test", "-q", "-p", pkg, "--", "--list"]).output().ok()?;
|
||||
if !out.status.success() {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
String::from_utf8_lossy(&out.stdout)
|
||||
.lines()
|
||||
.filter(|l| l.ends_with(": test"))
|
||||
.count(),
|
||||
)
|
||||
Some(String::from_utf8_lossy(&out.stdout).lines().filter(|l| l.ends_with(": test")).count())
|
||||
}
|
||||
|
||||
pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
|
||||
@@ -75,10 +67,7 @@ pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
|
||||
let mut rows = Vec::new();
|
||||
for p in meta["packages"].as_array().into_iter().flatten() {
|
||||
let name = p["name"].as_str().unwrap_or("?").to_owned();
|
||||
let Some(dir) = p["manifest_path"]
|
||||
.as_str()
|
||||
.and_then(|m| Path::new(m).parent().map(Path::to_path_buf))
|
||||
else {
|
||||
let Some(dir) = p["manifest_path"].as_str().and_then(|m| Path::new(m).parent().map(Path::to_path_buf)) else {
|
||||
continue;
|
||||
};
|
||||
let mut c = Counts::default();
|
||||
@@ -88,15 +77,8 @@ pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
|
||||
}
|
||||
rows.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let exact_col = if exact {
|
||||
format!(" {:>8}", "harness")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
println!(
|
||||
"{:<26} {:>6} {:>8} {:>7} {:>10}{exact_col}",
|
||||
"crate", "files", "lines", "#[test]", "proptest!"
|
||||
);
|
||||
let exact_col = if exact { format!(" {:>8}", "harness") } else { String::new() };
|
||||
println!("{:<26} {:>6} {:>8} {:>7} {:>10}{exact_col}", "crate", "files", "lines", "#[test]", "proptest!");
|
||||
println!("{}", "-".repeat(62 + if exact { 9 } else { 0 }));
|
||||
let mut total = Counts::default();
|
||||
let mut total_exact = 0;
|
||||
@@ -109,29 +91,17 @@ pub fn run(_root: &Path, exact: bool) -> Result<(), String> {
|
||||
}
|
||||
(true, None) => format!(" {:>8}", "error"),
|
||||
};
|
||||
println!(
|
||||
"{name:<26} {:>6} {:>8} {:>7} {:>10}{ex_s}",
|
||||
c.files, c.lines, c.tests, c.proptests
|
||||
);
|
||||
println!("{name:<26} {:>6} {:>8} {:>7} {:>10}{ex_s}", c.files, c.lines, c.tests, c.proptests);
|
||||
total.files += c.files;
|
||||
total.lines += c.lines;
|
||||
total.tests += c.tests;
|
||||
total.proptests += c.proptests;
|
||||
}
|
||||
println!("{}", "-".repeat(62 + if exact { 9 } else { 0 }));
|
||||
let ex_s = if exact {
|
||||
format!(" {total_exact:>8}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
println!(
|
||||
"{:<26} {:>6} {:>8} {:>7} {:>10}{ex_s}",
|
||||
"TOTAL", total.files, total.lines, total.tests, total.proptests
|
||||
);
|
||||
let ex_s = if exact { format!(" {total_exact:>8}") } else { String::new() };
|
||||
println!("{:<26} {:>6} {:>8} {:>7} {:>10}{ex_s}", "TOTAL", total.files, total.lines, total.tests, total.proptests);
|
||||
if !exact {
|
||||
println!(
|
||||
"\n(#[test] counts source attributes; macro-generated tests count once. Use --exact for harness counts.)"
|
||||
);
|
||||
println!("\n(#[test] counts source attributes; macro-generated tests count once. Use --exact for harness counts.)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -142,8 +112,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn counts_attributes_and_proptest_blocks() {
|
||||
let src =
|
||||
"#[test]\nfn a() {}\n// #[test] commented\n #[test]\nfn b() {}\nproptest! {\n}\n";
|
||||
let src = "#[test]\nfn a() {}\n// #[test] commented\n #[test]\nfn b() {}\nproptest! {\n}\n";
|
||||
assert_eq!(count_tests(src), (2, 1));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user