Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ Building the 0.0 pilot — no released artifact yet.
low-confidence shift); and the null-diff verdict ladder ported verbatim from
`experiments/null_diff.py` (−80/−40/−12 dB thresholds, relative-not-absolute
reasoning) — [PR #27](https://github.com/sep-lab/Wit/pull/27)
- **M5 (first slice)** — `wit-demo` and `wit demo-library`: a deterministic generator that
writes a synthetic `~/Music`-shaped tree (two Logic bundles, one with two alternatives;
a GarageBand bundle with no backups, matching the real shape; and a five-save Ableton
lineage in Live's `Backup/` layout — 21 versions total). The files carry real
`ProjectData` and `.als` framing with only the fields Wit extracts, so `wit scan`,
`logic-report` and `diff-als` all read them, but Logic and Live cannot open them and the
command says so. The Logic chain is shaped to the measured 33% empty-verdict rate rather
than to look impressive. Refuses any destination that is not empty, so it cannot
overwrite a real library. This also fixes `just demo-library`, which called a
`demo-library` subcommand that had never existed — [#18](https://github.com/sep-lab/Wit/issues/18)
- Research findings across Ableton `.als`, Logic `ProjectData`, GarageBand `.band` and
FL Studio `.flp`, measured on real projects ([docs/EXPERIMENTS.md](docs/EXPERIMENTS.md))
- Working prototypes: Ableton semantic differ, CDC dedup harness, FLP parser, storage bench
Expand Down
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/wit-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ wit-als = { path = "../wit-als" }
wit-diff = { path = "../wit-diff" }
wit-logic = { path = "../wit-logic" }
wit-index = { path = "../wit-index" }
wit-demo = { path = "../wit-demo" }
clap = { version = "4.5", features = ["derive"] }

[dev-dependencies]
Expand Down
33 changes: 31 additions & 2 deletions crates/wit-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
//! `dupes` (`wit-index`) — the first commands that persist anything, via
//! the one crate in the workspace allowed to write. M2.5 adds
//! `logic-report` — the issue #15 reality-gate tool, running `logic-probe`'s
//! comparison across an entire library instead of one pair. `wit log`/
//! `diff`/`report` land later; see `docs/ROADMAP.md`.
//! comparison across an entire library instead of one pair. M5 adds
//! `demo-library` (`wit-demo`), which writes the synthetic library the app
//! is developed and demoed against, so neither needs a real Logic library
//! on the machine. `wit log`/`diff`/`report` land later; see
//! `docs/ROADMAP.md`.

use clap::{Parser, Subcommand};
use std::collections::BTreeSet;
Expand Down Expand Up @@ -58,6 +61,11 @@ enum Command {
/// comparison on every consecutive pair, and print the empty-verdict
/// rate across the whole library. Read-only.
LogicReport { path: PathBuf },
/// Write a synthetic `~/Music`-shaped library to `dest` — two Logic
/// projects, a GarageBand project, and an Ableton lineage — so the app
/// is demoable on a machine with no real Logic library. Refuses to
/// write into a directory that already has anything in it.
DemoLibrary { dest: PathBuf },
}

fn main() -> ExitCode {
Expand All @@ -68,9 +76,30 @@ fn main() -> ExitCode {
Command::Scan { path, data_dir } => scan(&path, data_dir),
Command::Dupes { path } => dupes(&path),
Command::LogicReport { path } => logic_report(&path),
Command::DemoLibrary { dest } => demo_library(&dest),
}
}

/// M5 (issue #18): build the synthetic library `just demo-library` wraps.
fn demo_library(dest: &std::path::Path) -> ExitCode {
let lib = match wit_demo::build_demo_library(dest) {
Ok(lib) => lib,
Err(e) => {
eprintln!("wit: {e}");
return ExitCode::FAILURE;
}
};
println!(
" wrote {} Logic project(s), {} GarageBand project(s), {} Ableton lineage(s) — {} version(s) total",
lib.logic_projects, lib.garageband_projects, lib.ableton_lineages, lib.total_versions
);
println!(
" these are synthetic fixtures for Wit's own readers — Logic and Live cannot open them"
);
println!(" point the app at: {}", lib.root.display());
ExitCode::SUCCESS
}

/// The default index location: `~/Library/Application Support/Wit` on
/// macOS (the only platform the 0.0 pilot targets — ADR-0006). Falls back
/// to a `wit-data` directory under the current directory if `$HOME` isn't
Expand Down
80 changes: 80 additions & 0 deletions crates/wit-cli/tests/demo_library.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! End-to-end tests for `wit demo-library` — the M5 (issue #18) recipe that
//! makes the app demoable on a machine with no real Logic library.
//!
//! `wit-demo`'s own tests cover what the generated files contain; these
//! cover the thing only the binary can prove — that the command wires up,
//! reports honestly, and refuses the one input that could destroy something.

use std::process::Command;

fn wit(args: &[&str]) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_wit"))
.args(args)
.output()
.unwrap()
}

fn tempfile_dir(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"wit-demo-test-{}-{}-{}",
std::process::id(),
tag,
format!("{:?}", std::thread::current().id()).replace(['(', ')'], "")
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
dir
}

#[test]
fn demo_library_writes_a_library_the_other_commands_can_read() {
let dir = tempfile_dir("build");
let dest = dir.join("library");

let out = wit(&["demo-library", dest.to_str().unwrap()]);
assert!(out.status.success(), "demo-library should succeed");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("2 Logic project(s)") && stdout.contains("21 version(s)"),
"unexpected report: {stdout}"
);
// The honesty line is not decoration — a demo fixture must never be
// mistaken for something Logic could open.
assert!(stdout.contains("Logic and Live cannot open them"));

// The point of the whole command: `wit scan` finds what it wrote.
let scan = wit(&[
"scan",
dest.to_str().unwrap(),
"--data-dir",
dir.join("index").to_str().unwrap(),
]);
assert!(scan.status.success());
let scanned = String::from_utf8_lossy(&scan.stdout);
assert!(
scanned.contains("3 Logic/GarageBand project(s), 1 Ableton lineage(s)"),
"scan did not discover the demo library: {scanned}"
);
assert!(scanned.contains("Coastline (logic): 10 version(s)"));

std::fs::remove_dir_all(&dir).unwrap();
}

#[test]
fn demo_library_refuses_a_non_empty_destination_and_changes_nothing() {
let dir = tempfile_dir("refuse");
let precious = dir.join("MyRealSong.logicx");
std::fs::write(&precious, b"not a demo").unwrap();

let out = wit(&["demo-library", dir.to_str().unwrap()]);
assert!(!out.status.success(), "must refuse a non-empty destination");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("refusing to write a demo library over it"),
"the refusal must say why: {stderr}"
);
assert_eq!(std::fs::read(&precious).unwrap(), b"not a demo");
assert!(!dir.join("Logic").exists());

std::fs::remove_dir_all(&dir).unwrap();
}
21 changes: 21 additions & 0 deletions crates/wit-demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "wit-demo"
version = "0.0.0"
description = "Synthetic demo library generator: writes a ~/Music-shaped tree of Logic/GarageBand bundles and an Ableton lineage, so first-run, the timeline, and the watcher are demoable on any machine. Deterministic; never touches a real project."
edition.workspace = true
license.workspace = true
publish.workspace = true

[lints]
workspace = true

[dependencies]
flate2 = "1.0"

[dev-dependencies]
tempfile = "3"
wit-als = { path = "../wit-als" }
wit-diff = { path = "../wit-diff" }
wit-index = { path = "../wit-index" }
wit-logic = { path = "../wit-logic" }
wit-model = { path = "../wit-model" }
Loading
Loading