aboutsummaryrefslogtreecommitdiff
path: root/.run/src/bin
diff options
context:
space:
mode:
Diffstat (limited to '.run/src/bin')
-rw-r--r--.run/src/bin/ci.py75
-rw-r--r--.run/src/bin/clippy-fix.ps18
-rwxr-xr-x.run/src/bin/clippy-fix.sh6
-rw-r--r--.run/src/bin/cov-test.rs571
-rw-r--r--.run/src/bin/deploy-api-docs.rs118
-rw-r--r--.run/src/bin/display-dependency-order.rs12
-rw-r--r--.run/src/bin/http-page-preview.ps13
-rwxr-xr-x.run/src/bin/http-page-preview.sh2
-rw-r--r--.run/src/bin/install-mling.ps110
-rwxr-xr-x.run/src/bin/install-mling.sh17
-rw-r--r--.run/src/bin/package-all.rs736
-rw-r--r--.run/src/bin/update-version.rs104
-rw-r--r--.run/src/bin/windows-folder-hide.ps1115
13 files changed, 0 insertions, 1777 deletions
diff --git a/.run/src/bin/ci.py b/.run/src/bin/ci.py
deleted file mode 100644
index 6234a19..0000000
--- a/.run/src/bin/ci.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""Full CI orchestration for the mingling project.
-
-Runs every `cargo ci` step in order: lock the workspace, run all checks,
-refresh the generated artifacts, then unlock. The final `git-unlock` doubles
-as the idempotency check: it fails with a non-zero exit code when the run
-left the working tree dirty.
-
-The script locates the git repository root and runs with it as the working
-directory, so it can be invoked from anywhere inside the repo.
-"""
-
-import os
-import subprocess
-import sys
-from pathlib import Path
-
-# The pipeline steps, in execution order, as (command, args) pairs.
-STEPS: list[tuple[str, list[str]]] = [
- ("git-lock", []),
- ("report-clean", []),
- ("build-check", []),
- ("clippy-check", []),
- ("test-all", []),
- ("example-check", []),
- ("docs-check", []),
- ("example-refresh", []),
- ("docsify-refresh", []),
- ("features-refresh", []),
- # Idempotency check: exits non-zero if CI contaminated the workspace, and
- # prints the diff of the contamination before restoring.
- ("git-unlock", ["--show-diff"]),
-]
-
-
-def find_repo_root() -> Path:
- """Return the nearest ancestor directory containing `.git`."""
- current = Path.cwd()
- for directory in (current, *current.parents):
- if (directory / ".git").is_dir():
- return directory
- raise SystemExit("error: not inside a git repository")
-
-
-def main() -> int:
- root = find_repo_root()
- os.chdir(root)
-
- # Signature banner: docs/res/ci_banner.txt, relative to this script
- # (.run/src/bin -> four levels up is the repo root).
- banner = (
- Path(__file__).resolve().parent.parent.parent.parent
- / "docs"
- / "res"
- / "ci_banner.txt"
- )
- try:
- print(banner.read_text(encoding="utf-8"), end="")
- except OSError:
- pass
-
- for command, args in STEPS:
- print(f"==> cargo ci {' '.join([command, *args])}")
- result = subprocess.run(["cargo", "ci", command, *args], check=False)
- if result.returncode != 0:
- print(
- f"error: step `{command}` failed with exit code {result.returncode}",
- file=sys.stderr,
- )
- return result.returncode
-
- return 0
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.run/src/bin/clippy-fix.ps1 b/.run/src/bin/clippy-fix.ps1
deleted file mode 100644
index 1d24f92..0000000
--- a/.run/src/bin/clippy-fix.ps1
+++ /dev/null
@@ -1,8 +0,0 @@
-$starting_dir = Get-Location
-Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
- $project_dir = $_.DirectoryName
- Push-Location $project_dir
- cargo clippy --fix --allow-dirty --allow-no-vcs --quiet
- Pop-Location
-}
-Set-Location $starting_dir
diff --git a/.run/src/bin/clippy-fix.sh b/.run/src/bin/clippy-fix.sh
deleted file mode 100755
index 9771ad4..0000000
--- a/.run/src/bin/clippy-fix.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find . -name "Cargo.toml" -type f | while read -r cargo_file; do
- project_dir=$(dirname "$cargo_file")
- (cd "$project_dir" && cargo clippy --fix --allow-dirty --allow-no-vcs --quiet)
-done
diff --git a/.run/src/bin/cov-test.rs b/.run/src/bin/cov-test.rs
deleted file mode 100644
index f62ff01..0000000
--- a/.run/src/bin/cov-test.rs
+++ /dev/null
@@ -1,571 +0,0 @@
-//! Coverage test generator for mingling.
-//!
-//! This script requires the **fork** of cargo-llvm-cov:
-//! <https://github.com/Weicao-CatilGrass/cargo-llvm-cov>
-//!
-//! The upstream `report` command cannot include binaries of non-workspace
-//! crates (examples and test crates) and unconditionally filters
-//! `tests`/`examples` source files. The fork adds two flags to fix this:
-//!
-//! - `--object <PATH>`: include arbitrary binaries in the report
-//! (upstream issue taiki-e/cargo-llvm-cov#367)
-//! - `--include-examples`: stop filtering source files under the
-//! `examples` directory (upstream issue taiki-e/cargo-llvm-cov#503)
-//!
-//! The script itself does not use `--include-examples`; it passes
-//! `--no-default-ignore-filename-regex` and supplies its own filter so that
-//! `tests`/`benches` directories stay in the report too.
-//!
-//! Install it with:
-//!
-//! ```bash
-//! cargo install --git https://github.com/Weicao-CatilGrass/cargo-llvm-cov cargo-llvm-cov
-//! ```
-
-use std::fs;
-use std::path::{Path, PathBuf};
-
-use serde::Deserialize;
-use tools::{eprintln_cargo_style, println_cargo_style, run_cmd};
-
-const OUTPUT_DIR: &str = "docs/cov-test";
-
-/// Shared target directory for all `cargo llvm-cov` runs.
-///
-/// Pointing every run at the same target dir makes all of them share the
-/// instrumented build cache and, more importantly, accumulate profraw files
-/// in one place so the final `report` can merge everything.
-const COV_TARGET_DIR: &str = ".temp/cov-llvm";
-
-/// An example's `test.toml` (`[[runs]]` entries).
-#[derive(Deserialize)]
-struct TestConfig {
- runs: Vec<TestCase>,
-}
-
-/// One `[[runs]]` entry of an example's `test.toml`.
-#[derive(Deserialize)]
-struct TestCase {
- input: Vec<String>,
-}
-
-fn main() {
- let repo_root = find_git_repo().expect("Failed to find git repository root");
- let output_path = repo_root.join(OUTPUT_DIR);
- let cov_target = repo_root.join(COV_TARGET_DIR);
-
- // Read features from [package.metadata.docs.rs]
- let features = tools::read_features().unwrap_or_else(|e| {
- eprintln!("Error: {}", e);
- std::process::exit(1);
- });
- let features_arg = features.join(",");
-
- // Ensure output directory exists
- std::fs::create_dir_all(&output_path).expect("Failed to create output directory");
- std::fs::create_dir_all(&cov_target).expect("Failed to create cov target directory");
-
- // All `cargo llvm-cov` invocations below share one target dir, so profraw
- // files accumulate and are merged by the final `report` command.
- // SAFETY: set before any thread is spawned; this process only shells out
- // to subcommands via std::process.
- unsafe {
- std::env::set_var("CARGO_LLVM_COV_TARGET_DIR", &cov_target);
- }
-
- // Drop stale profraw from previous runs (keep the instrumented build cache).
- clean_old_profraw(&cov_target);
-
- println_cargo_style!("Features: {}", features_arg);
- println_cargo_style!("Target: {}", cov_target.display());
-
- // 1. Workspace tests
- println_cargo_style!("Running: cargo llvm-cov test --workspace");
- run_cmd!(format!(
- "cargo llvm-cov test --no-report --workspace --features \"{}\" --color always",
- features_arg
- ))
- .unwrap_or_else(|code| {
- eprintln_cargo_style!("workspace tests failed with exit code {}", code);
- std::process::exit(code);
- });
-
- // 2. Integration test crates under mingling_core/tests (excluded from the
- // workspace, so they need their own `--manifest-path` runs)
- for manifest in find_test_crate_manifests(&repo_root) {
- println_cargo_style!(
- "Running: cargo llvm-cov test {}",
- manifest.file_name().unwrap_or_default().to_string_lossy()
- );
- run_cmd!(format!(
- "cargo llvm-cov test --no-report --manifest-path \"{}\" --color always",
- manifest.display()
- ))
- .unwrap_or_else(|code| {
- eprintln_cargo_style!(
- "test crate {} failed with exit code {}",
- manifest.display(),
- code
- );
- std::process::exit(code);
- });
- }
-
- // 3. Examples: build each example with explicit RUSTFLAGS, then execute
- // every command declared in the example's test.toml directly.
- //
- // NOTE: `cargo llvm-cov run` cannot be used here. Its rustc wrapper
- // only instruments the crates of the *current* cargo project (with
- // `--manifest-path` that is the example itself), so the mingling
- // libraries — being dependencies — would not be instrumented and their
- // coverage would silently be lost (once_exec.rs showed 0%). Building
- // with plain RUSTFLAGS instruments the whole dependency graph.
- //
- // RUSTFLAGS/CARGO_TARGET_DIR are set process-wide here because only the
- // `report` step (which does not compile) follows. Non-zero exit codes
- // are expected for some examples (e.g. `--help` exits with 2); profraw
- // is still written.
- unsafe {
- std::env::set_var("RUSTFLAGS", "-Cinstrument-coverage");
- std::env::set_var("CARGO_TARGET_DIR", &cov_target);
- }
- let examples = load_example_commands(&repo_root);
- let mut built = std::collections::HashSet::new();
- for (example, input) in &examples {
- if built.insert(example.clone()) {
- println_cargo_style!("Building: {}", example);
- run_cmd!(format!(
- "cargo build --manifest-path examples/{}/Cargo.toml --color always",
- example
- ))
- .unwrap_or_else(|code| {
- eprintln_cargo_style!(
- "build of example {} failed with exit code {}",
- example,
- code
- );
- std::process::exit(code);
- });
- }
- let binary = cov_target.join("debug").join(get_binary_name(example));
- let profraw = format!(
- "{}/example-{}.%p.profraw",
- cov_target.to_string_lossy(),
- example
- );
- match std::process::Command::new(&binary)
- .args(input)
- .env("LLVM_PROFILE_FILE", &profraw)
- .status()
- {
- Ok(status) if status.success() => {}
- Ok(status) => println_cargo_style!(
- "Warning: example {} exited with {:?}, profraw still recorded",
- example,
- status.code()
- ),
- Err(e) => eprintln_cargo_style!("Failed to run example {}: {}", example, e),
- }
- }
-
- // 4. Collect the binaries of non-workspace crates (examples + test crates).
- // The automatic object-file detection only knows workspace members, so
- // these must be passed explicitly via --object.
- let member_names = workspace_member_names(&repo_root);
- let object_args = collect_object_args(&cov_target, &member_names);
-
- // 5. Generate the merged HTML report.
- //
- // --no-default-ignore-filename-regex: the default regex unconditionally
- // excludes `examples`/`tests` directories, which is exactly what we want
- // to include here, so we take over the filter ourselves.
- let ignore_re = build_ignore_regex(&cov_target);
- println_cargo_style!("Running: cargo llvm-cov report --html");
- run_cmd!(format!(
- "cargo llvm-cov report --html --output-dir \"{}\" --no-default-ignore-filename-regex --ignore-filename-regex \"{}\" {} --color always",
- output_path.to_string_lossy(),
- ignore_re,
- object_args
- ))
- .unwrap_or_else(|code| {
- eprintln_cargo_style!("cargo llvm-cov report failed with exit code {}", code);
- std::process::exit(code);
- });
-
- // Move files from <output_path>/html/ to <output_path>
- let html_dir = output_path.join("html");
- if html_dir.exists() && html_dir.is_dir() {
- println_cargo_style!("Moving files from {}/html/ to {}/", OUTPUT_DIR, OUTPUT_DIR);
-
- for entry in fs::read_dir(&html_dir).expect("Failed to read html directory") {
- let entry = entry.expect("Failed to read entry");
- let entry_path = entry.path();
- let file_name = entry
- .file_name()
- .to_str()
- .expect("Invalid filename")
- .to_owned();
-
- let dest_path = output_path.join(&file_name);
- if dest_path.exists() {
- if dest_path.is_dir() {
- fs::remove_dir_all(&dest_path).unwrap_or_else(|e| {
- eprintln!(
- "Warning: could not remove directory {}: {}",
- dest_path.display(),
- e
- );
- });
- } else {
- fs::remove_file(&dest_path).unwrap_or_else(|e| {
- eprintln!(
- "Warning: could not remove file {}: {}",
- dest_path.display(),
- e
- );
- });
- }
- }
- fs::rename(&entry_path, &dest_path).unwrap_or_else(|e| {
- eprintln!("Warning: could not move {}: {}", entry_path.display(), e);
- });
- }
-
- fs::remove_dir(&html_dir).unwrap_or_else(|e| {
- eprintln!("Warning: could not remove html directory: {}", e);
- });
-
- println_cargo_style!("Files moved successfully.");
- }
-
- // 6. Recolor the per-file coverage summary with project-specific
- // thresholds: 0-50% red, 51-80% yellow, 81-100% green. llvm-cov's
- // built-in thresholds differ, and the color is assigned when the HTML
- // is generated, so the summary table is rewritten here.
- let index_path = output_path.join("index.html");
- if let Err(e) = recolor_report_index(&index_path) {
- eprintln_cargo_style!("Warning: failed to recolor {}: {}", index_path.display(), e);
- }
-
- println_cargo_style!(
- "Done: coverage report generated at {}/index.html",
- OUTPUT_DIR
- );
-}
-
-/// Remove `*.profraw` from the shared target dir so stale data from previous
-/// runs does not pollute the merged report. The instrumented build cache
-/// (everything else) is kept.
-fn clean_old_profraw(cov_target: &Path) {
- if let Ok(entries) = fs::read_dir(cov_target) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.extension().is_some_and(|e| e == "profraw") {
- let _ = fs::remove_file(&path);
- }
- }
- }
-}
-
-/// All `mingling_core/tests/<crate>/Cargo.toml` manifests.
-fn find_test_crate_manifests(repo_root: &Path) -> Vec<PathBuf> {
- let tests_dir = repo_root.join("mingling_core/tests");
- let mut manifests = Vec::new();
- if let Ok(entries) = fs::read_dir(&tests_dir) {
- for entry in entries.flatten() {
- let manifest = entry.path().join("Cargo.toml");
- if manifest.is_file() {
- manifests.push(manifest);
- }
- }
- }
- manifests.sort();
- manifests
-}
-
-/// Parse every `examples/<name>/test.toml` into `(example_name, input)` pairs.
-fn load_example_commands(repo_root: &Path) -> Vec<(String, Vec<String>)> {
- let examples_dir = repo_root.join("examples");
- let mut entries: Vec<_> = std::fs::read_dir(&examples_dir)
- .unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read {}: {}", examples_dir.display(), e);
- std::process::exit(1);
- })
- .flatten()
- .collect();
- entries.sort_by_key(|e| e.file_name());
-
- let mut pairs = Vec::new();
- for entry in entries {
- let path = entry.path();
- if !path.is_dir() {
- continue;
- }
- let test_toml = path.join("test.toml");
- if !test_toml.is_file() {
- continue;
- }
- let name = path
- .file_name()
- .and_then(|n| n.to_str())
- .unwrap_or_default()
- .to_string();
- let content = fs::read_to_string(&test_toml).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read {}: {}", test_toml.display(), e);
- std::process::exit(1);
- });
- let config: TestConfig = toml::from_str(&content).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to parse {}: {}", test_toml.display(), e);
- std::process::exit(1);
- });
- for case in config.runs {
- pairs.push((name.clone(), case.input));
- }
- }
- pairs
-}
-
-/// Names of all workspace members, from `cargo metadata --no-deps`.
-fn workspace_member_names(repo_root: &Path) -> Vec<String> {
- let Ok(output) = tools::run_cmd_capture_with_dir(
- "cargo metadata --no-deps --format-version 1".to_string(),
- repo_root,
- ) else {
- return Vec::new();
- };
- let Ok(json) = serde_json::from_str::<serde_json::Value>(&output) else {
- return Vec::new();
- };
- json["packages"]
- .as_array()
- .into_iter()
- .flatten()
- .filter_map(|p| p["name"].as_str().map(str::to_owned))
- .collect()
-}
-
-/// Collect the binaries of non-workspace crates (examples and test crates)
-/// from the shared target dir, as `--object <path>` arguments.
-///
-/// - `debug/` root: example binaries (built via `cargo llvm-cov run`).
-/// - `debug/deps/`: test crate binaries (e.g. `integration-<hash>`); their
-/// names do not follow a single pattern, so anything that is not a
-/// workspace-member binary and not a proc-macro `.so` is collected.
-///
-/// Workspace member binaries are detected automatically by `report` and must
-/// NOT be passed again (duplicate `-object` entries produce duplicated
-/// output). Hard links to the same file are deduplicated by inode.
-fn collect_object_args(cov_target: &Path, member_names: &[String]) -> String {
- let debug_dir = cov_target.join("debug");
- let mut objects = Vec::new();
- let mut seen = std::collections::HashSet::new();
-
- for dir in [debug_dir.clone(), debug_dir.join("deps")] {
- let Ok(entries) = fs::read_dir(&dir) else {
- continue;
- };
- for entry in entries.flatten() {
- let path = entry.path();
- if !path.is_file() || !is_executable(&path) {
- continue;
- }
- if !seen.insert(file_id(&path)) {
- continue;
- }
- let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
- continue;
- };
- // Proc-macro shared objects are either workspace members (picked
- // up automatically) or external deps (excluded from the report
- // by the ignore regex), so never pass them explicitly.
- if name.starts_with("lib") && name.ends_with(".so") {
- continue;
- }
- if is_workspace_member_binary(name, member_names) {
- continue;
- }
- objects.push(path);
- }
- }
-
- objects.sort();
- objects
- .iter()
- .map(|p| format!("--object \"{}\"", p.to_string_lossy()))
- .collect::<Vec<_>>()
- .join(" ")
-}
-
-/// True if the binary name (e.g. `mingling_core-fea14a01b88afcaa`) belongs to
-/// a workspace member.
-fn is_workspace_member_binary(name: &str, member_names: &[String]) -> bool {
- let stem = strip_cargo_hash(name);
- member_names.iter().any(|m| stem == m)
-}
-
-/// Strip the cargo-generated hash suffix: `mingling_core-fea14a01b88afcaa` ->
-/// `mingling_core`. Returns the input unchanged if there is no such suffix.
-fn strip_cargo_hash(name: &str) -> &str {
- let Some(idx) = name.rfind('-') else {
- return name;
- };
- let (head, tail) = name.split_at(idx);
- let hash = &tail[1..];
- if hash.len() == 16 && hash.chars().all(|c| c.is_ascii_hexdigit()) {
- head
- } else {
- name
- }
-}
-
-/// A stable identity for deduplicating hard links: device+inode on Unix,
-/// canonicalized path elsewhere.
-fn file_id(path: &Path) -> String {
- #[cfg(unix)]
- {
- use std::os::unix::fs::MetadataExt as _;
- if let Ok(metadata) = fs::metadata(path) {
- return format!("{}:{}", metadata.dev(), metadata.ino());
- }
- }
- fs::canonicalize(path)
- .unwrap_or_else(|_| path.to_path_buf())
- .to_string_lossy()
- .into_owned()
-}
-
-/// Resolve binary filename for the given example.
-///
-/// The binary name matches the package name. On Windows, the `.exe` suffix is
-/// required.
-fn get_binary_name(example_name: &str) -> String {
- let base = example_name;
- if cfg!(target_os = "windows") {
- format!("{base}.exe")
- } else {
- base.to_string()
- }
-}
-
-/// Rewrite the per-file coverage colors in `index.html` with project-specific
-/// thresholds: 0-50% red, 51-80% yellow, 81-100% green.
-fn recolor_report_index(index_path: &Path) -> std::io::Result<()> {
- let content = fs::read_to_string(index_path)?;
- fs::write(index_path, recolor_coverage_table(&content))
-}
-
-/// Recolor every `<td class='column-entry-...'><pre>XX% ...</pre></td>` cell
-/// in the coverage summary table according to the new thresholds. Cells with
-/// no data (e.g. branch coverage `- (0/0)`, class `gray`) are left as-is.
-fn recolor_coverage_table(input: &str) -> String {
- const TD: &str = "<td class='column-entry-";
- let mut out = String::with_capacity(input.len());
- let mut rest = input;
- while let Some(pos) = rest.find(TD) {
- out.push_str(&rest[..pos + TD.len()]);
- rest = &rest[pos + TD.len()..];
- let Some(pre_end) = rest.find("'><pre>") else {
- out.push_str(rest);
- return out;
- };
- let color = &rest[..pre_end];
- let tail = &rest[pre_end + "'><pre>".len()..];
- let pct: String = tail
- .trim_start()
- .chars()
- .take_while(|c| c.is_ascii_digit() || *c == '.')
- .collect();
- let new_color = match pct.parse::<f64>() {
- Ok(v) if v <= 50.0 => "red",
- Ok(v) if v <= 80.0 => "yellow",
- Ok(_) => "green",
- Err(_) => color, // no data (e.g. gray branch column)
- };
- out.push_str(new_color);
- out.push_str("'><pre>");
- rest = tail;
- }
- out.push_str(rest);
- out
-}
-
-/// True if the file is executable: mode bits on Unix, `.exe` on Windows.
-fn is_executable(path: &Path) -> bool {
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt as _;
- let Ok(metadata) = std::fs::metadata(path) else {
- return false;
- };
- metadata.permissions().mode() & 0o111 != 0
- }
- #[cfg(not(unix))]
- {
- path.extension()
- .is_some_and(|e| e.eq_ignore_ascii_case("exe"))
- }
-}
-
-/// Regex that keeps only the project's own sources in the report:
-/// excludes the shared llvm-cov target dir, the standard library, and
-/// external dependencies.
-fn build_ignore_regex(cov_target: &Path) -> String {
- let target = regex_escape_path(cov_target);
- format!(
- "^{target}($|/)|/rustc/([0-9a-f]+|[0-9]+\\.[0-9]+\\.[0-9]+)/|/\\.cargo/(registry|git)/|/\\.rustup/toolchains($|/)"
- )
-}
-
-/// Escape a path for use inside a regular expression (as a literal prefix).
-fn regex_escape_path(path: &Path) -> String {
- let s = path.to_string_lossy().replace('\\', "/");
- let mut escaped = String::with_capacity(s.len());
- for ch in s.chars() {
- if ch == '.' || ch == '-' {
- escaped.push('\\');
- }
- escaped.push(ch);
- }
- escaped
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
-
-#[cfg(test)]
-mod tests {
- use super::recolor_coverage_table;
-
- #[test]
- fn recolor_thresholds() {
- let input = concat!(
- "<td class='column-entry-red'><pre> 50.00% (2/4)</pre></td>",
- "<td class='column-entry-yellow'><pre> 51.23% (32/52)</pre></td>",
- "<td class='column-entry-red'><pre> 80.00% (48/89)</pre></td>",
- "<td class='column-entry-green'><pre> 81.00% (1/1)</pre></td>",
- "<td class='column-entry-yellow'><pre> 90.00% (6/7)</pre></td>",
- "<td class='column-entry-gray'><pre>- (0/0)</pre></td>",
- );
- let out = recolor_coverage_table(input);
- assert!(out.contains("class='column-entry-red'><pre> 50.00%"));
- assert!(out.contains("class='column-entry-yellow'><pre> 51.23%"));
- assert!(out.contains("class='column-entry-yellow'><pre> 80.00%"));
- assert!(out.contains("class='column-entry-green'><pre> 81.00%"));
- assert!(out.contains("class='column-entry-green'><pre> 90.00%"));
- assert!(out.contains("class='column-entry-gray'><pre>- (0/0)"));
- }
-}
diff --git a/.run/src/bin/deploy-api-docs.rs b/.run/src/bin/deploy-api-docs.rs
deleted file mode 100644
index 961eb04..0000000
--- a/.run/src/bin/deploy-api-docs.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-use std::path::Path;
-
-use arg_picker::{Picker, macros::arg};
-use tools::{println_cargo_style, run_cmd};
-
-const OUTPUT_DIR: &str = "docs/api-docs";
-
-fn main() {
- let using_docsrs = Picker::from_args()
- .pick_or_default(&arg![docsrs: bool])
- .unwrap();
-
- let repo_root = find_git_repo().expect("Failed to find git repository root");
-
- // Read features from [package.metadata.docs.rs]
- let features = tools::read_features().unwrap_or_else(|e| {
- eprintln!("Error: {}", e);
- std::process::exit(1);
- });
-
- let features_arg = features.join(",");
-
- // Ensure output directory exists
- let output_path = repo_root.join(OUTPUT_DIR);
- std::fs::create_dir_all(&output_path).expect("Failed to create output directory");
-
- // Build cargo doc command
- let cmd = if using_docsrs {
- format!(
- "cargo +nightly rustdoc --features \"{}\" -p mingling --target-dir \"{}\" --color always -- --cfg docsrs",
- features_arg,
- output_path.join("target").to_string_lossy()
- )
- } else {
- format!(
- "cargo doc --no-deps --features \"{}\" -p mingling --target-dir \"{}\" --color always",
- features_arg,
- output_path.join("target").to_string_lossy()
- )
- };
-
- println_cargo_style!("Features: {}", features_arg);
- println_cargo_style!("Output: {}", output_path.display());
-
- // Run cargo doc, then copy generated docs to output directory
- println_cargo_style!("Building: docs (cargo doc --no-deps)");
- run_cmd!(&cmd).unwrap_or_else(|code| {
- eprintln!("Error: cargo doc failed with exit code {}", code);
- std::process::exit(code);
- });
-
- // Copy generated docs from target/doc to OUTPUT_DIR (top level)
- let doc_source = output_path.join("target").join("doc");
- let doc_dest = &output_path;
-
- if doc_source.exists() {
- println_cargo_style!("Copying: docs to output directory");
- // Remove old docs in destination (except target/)
- if let Ok(entries) = std::fs::read_dir(doc_dest) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.file_name().and_then(|n| n.to_str()) == Some("target") {
- continue;
- }
- if path.is_dir() {
- std::fs::remove_dir_all(&path).ok();
- } else {
- std::fs::remove_file(&path).ok();
- }
- }
- }
- copy_dir_recursively(&doc_source, doc_dest).expect("Failed to copy documentation");
- }
-
- // Clean up the intermediate target directory to save space
- std::fs::remove_dir_all(output_path.join("target")).ok();
-
- println_cargo_style!("Done: API docs deployed to {}", output_path.display());
-}
-
-fn copy_dir_recursively(src: &Path, dst: &Path) -> std::io::Result<()> {
- if !dst.exists() {
- std::fs::create_dir_all(dst)?;
- }
-
- for entry in std::fs::read_dir(src)? {
- let entry = entry?;
- let file_type = entry.file_type()?;
- let src_path = entry.path();
- let file_name = src_path.file_name().expect("Failed to get file name");
- let dst_path = dst.join(file_name);
-
- if file_type.is_dir() {
- copy_dir_recursively(&src_path, &dst_path)?;
- } else {
- std::fs::copy(&src_path, &dst_path)?;
- }
- }
-
- Ok(())
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
diff --git a/.run/src/bin/display-dependency-order.rs b/.run/src/bin/display-dependency-order.rs
deleted file mode 100644
index a31c67a..0000000
--- a/.run/src/bin/display-dependency-order.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use tools::{dependency_order::display_dependency_order, eprintln_cargo_style};
-
-fn main() {
- let order = display_dependency_order();
- if order.is_empty() {
- eprintln_cargo_style!("could not find workspace root or mingling crates");
- std::process::exit(1);
- }
- for path in order {
- println!("{}", path.display());
- }
-}
diff --git a/.run/src/bin/http-page-preview.ps1 b/.run/src/bin/http-page-preview.ps1
deleted file mode 100644
index 8cc3579..0000000
--- a/.run/src/bin/http-page-preview.ps1
+++ /dev/null
@@ -1,3 +0,0 @@
-$starting_dir = Get-Location
-python -m http.server 3000
-Set-Location $starting_dir
diff --git a/.run/src/bin/http-page-preview.sh b/.run/src/bin/http-page-preview.sh
deleted file mode 100755
index bed4b1c..0000000
--- a/.run/src/bin/http-page-preview.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-python3 -m http.server 3000
diff --git a/.run/src/bin/install-mling.ps1 b/.run/src/bin/install-mling.ps1
deleted file mode 100644
index 2b55a09..0000000
--- a/.run/src/bin/install-mling.ps1
+++ /dev/null
@@ -1,10 +0,0 @@
-$ErrorActionPreference = "Stop"
-
-cargo build --release --manifest-path mingling_cli/Cargo.toml
-
-New-Item -ItemType Directory -Force -Path .temp/mling/bin, .temp/mling/scripts | Out-Null
-
-Copy-Item .temp/target/release/mling.exe .temp/mling/bin/
-Copy-Item .temp/target/release/mingling-cli.exe .temp/mling/bin/
-Copy-Item .temp/target/mingling/mling_comp.ps1 .temp/mling/scripts/mling_comp.ps1
-Copy-Item mingling_cli/scripts/load_mling.ps1 .temp/mling/
diff --git a/.run/src/bin/install-mling.sh b/.run/src/bin/install-mling.sh
deleted file mode 100755
index e8cfa18..0000000
--- a/.run/src/bin/install-mling.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-set -e
-
-cargo build --release --manifest-path mingling_cli/Cargo.toml
-
-mkdir -p .temp/mling/bin .temp/mling/scripts
-
-cp .temp/target/release/mling .temp/mling/bin/
-cp .temp/target/release/mingling-cli .temp/mling/bin/
-
-for comp in zsh sh fish; do
- cp ".temp/target/mingling/mling_comp.$comp" ".temp/mling/scripts/mling_comp.$comp"
-done
-cp mingling_cli/scripts/load_mling.zsh .temp/mling/
-cp mingling_cli/scripts/load_mling.sh .temp/mling/
-cp mingling_cli/scripts/load_mling.fish .temp/mling/
diff --git a/.run/src/bin/package-all.rs b/.run/src/bin/package-all.rs
deleted file mode 100644
index ecdd133..0000000
--- a/.run/src/bin/package-all.rs
+++ /dev/null
@@ -1,736 +0,0 @@
-use std::collections::HashMap;
-use std::path::{Path, PathBuf};
-
-use flate2::read::GzDecoder;
-use serde::Deserialize;
-use tar::Archive;
-use toml::Table as TomlTable;
-use tools::{
- dependency_order::find_workspace_root, eprintln_cargo_style, println_cargo_style,
- run_cmd_capture_with_dir, wprintln_cargo_style,
-};
-
-/// A single member from `cargo metadata` output.
-#[derive(Deserialize, Debug)]
-struct MetadataPackage {
- name: String,
- version: String,
- manifest_path: String,
-}
-
-/// The top-level metadata structure.
-#[derive(Deserialize, Debug)]
-struct Metadata {
- #[allow(dead_code)]
- workspace_root: String,
- packages: Vec<MetadataPackage>,
-}
-
-fn main() {
- // 1. Determine project root
- let cwd = std::env::current_dir().expect("failed to get current working directory");
- let workspace_root = find_workspace_root(&cwd).expect("not inside a Cargo workspace");
- println_cargo_style!("Workspace: {}", workspace_root.display());
-
- let pre_release_dir = workspace_root.join(".temp/pre-release");
-
- // 2. Clean `.temp/pre-release/`
- println_cargo_style!("Clean: .temp/pre-release/");
- let _ = std::fs::remove_dir_all(&pre_release_dir);
- std::fs::create_dir_all(&pre_release_dir).expect("failed to create .temp/pre-release/");
-
- // 3. Run `cargo metadata` to get workspace members info
- println_cargo_style!("Metadata: querying workspace members");
- let metadata_json = run_cmd_capture_with_dir(
- "cargo metadata --format-version 1 --no-deps",
- &workspace_root,
- )
- .unwrap_or_else(|(code, _msg)| {
- eprintln_cargo_style!(format!("cargo metadata failed (exit {code}):\n{{msg}}"));
- std::process::exit(1);
- });
-
- let metadata: Metadata = serde_json::from_str(&metadata_json).unwrap_or_else(|e| {
- eprintln_cargo_style!("failed to parse cargo metadata: {}", e);
- std::process::exit(1);
- });
-
- // Filter workspace members: skip the root virtual manifest
- let workspace_root_str = workspace_root.to_string_lossy().replace('\\', "/");
- let members: Vec<&MetadataPackage> = metadata
- .packages
- .iter()
- .filter(|p| {
- let mp = p.manifest_path.replace('\\', "/");
- mp.starts_with(&workspace_root_str)
- && mp != format!("{}/Cargo.toml", workspace_root_str)
- })
- .collect();
-
- if members.is_empty() {
- eprintln_cargo_style!("No workspace members found!");
- std::process::exit(1);
- }
-
- // Print member info
- for m in &members {
- println_cargo_style!("Member: {}@{}", m.name, m.version);
- }
-
- // Build version map: crate_name -> version
- let mut version_map: HashMap<String, String> = HashMap::new();
- for m in &members {
- version_map.insert(m.name.clone(), m.version.clone());
- }
-
- // Collect unique member directories that need to be copied
- let mut member_dirs: Vec<PathBuf> = Vec::new();
- for m in &members {
- let dir = Path::new(&m.manifest_path)
- .parent()
- .expect("manifest_path has no parent");
- let canonical_dir = std::fs::canonicalize(dir).unwrap_or_else(|_| dir.to_path_buf());
- let canonical_root =
- std::fs::canonicalize(&workspace_root).unwrap_or_else(|_| workspace_root.to_path_buf());
- let relative = canonical_dir
- .strip_prefix(&canonical_root)
- .map(|p| p.to_path_buf())
- .unwrap_or_else(|_| PathBuf::from(dir.file_name().unwrap_or_default()));
- if !member_dirs.contains(&relative) {
- member_dirs.push(relative);
- }
- }
-
- // 4. Copy files to the temp directory, preserving the workspace directory structure
- println_cargo_style!("Copy: project structure to .temp/pre-release/");
-
- copy_dir(
- &workspace_root.join(".cargo"),
- &pre_release_dir.join(".cargo"),
- );
-
- for dir in &member_dirs {
- let src = workspace_root.join(dir);
- let dst = pre_release_dir.join(dir);
- copy_dir(&src, &dst);
- }
-
- copy_file(
- &workspace_root.join("Cargo.toml"),
- &pre_release_dir.join("Cargo.toml"),
- );
- copy_file(
- &workspace_root.join("Cargo.lock"),
- &pre_release_dir.join("Cargo.lock"),
- );
-
- // 5. Fully resolve ALL workspace inheritance in every member's Cargo.toml,
- // so each crate becomes monomorphic (no `workspace = true` references).
- // Then strip `[workspace.dependencies]` and `[workspace.package]` from the
- // root Cargo.toml, since they are no longer needed.
- println_cargo_style!("Resolve: inline all workspace inheritance");
-
- // Parse workspace config from the COPIED root Cargo.toml
- let root_cargo_path = pre_release_dir.join("Cargo.toml");
- let root_content = std::fs::read_to_string(&root_cargo_path)
- .unwrap_or_else(|e| panic!("failed to read {}: {e}", root_cargo_path.display()));
-
- let (ws_package, ws_deps) = parse_workspace_config(&root_content);
-
- // Resolve each member's Cargo.toml
- for dir in &member_dirs {
- let member_cargo = pre_release_dir.join(dir).join("Cargo.toml");
- if !member_cargo.exists() {
- continue;
- }
- let member_content = std::fs::read_to_string(&member_cargo)
- .unwrap_or_else(|e| panic!("failed to read {}: {e}", member_cargo.display()));
- let resolved =
- resolve_member_manifest(&member_content, dir, &ws_package, &ws_deps, &version_map);
- std::fs::write(&member_cargo, &resolved)
- .unwrap_or_else(|e| panic!("failed to write {}: {e}", member_cargo.display()));
- }
-
- // Strip [workspace.dependencies], [workspace.package], and root [package]
- // from root Cargo.toml, making it a pure virtual manifest.
- let stripped = strip_workspace_config(&root_content);
- std::fs::write(&root_cargo_path, &stripped)
- .unwrap_or_else(|e| panic!("failed to write {}: {e}", root_cargo_path.display()));
-
- println_cargo_style!("Package: running cargo package --workspace --no-verify");
-
- // 6. Run cargo package in the temp directory
- let package_ok = run_cmd_capture_with_dir(
- "cargo package --workspace --no-verify --color always",
- &pre_release_dir,
- );
-
- match &package_ok {
- Ok(out) => {
- println!("{out}");
- }
- Err((code, msg)) => {
- // Print output but don't fail yet
- eprintln_cargo_style!(format!("cargo package exited with code {code}:"));
- println!("{msg}");
- }
- }
-
- // 7. Copy built packages back to .temp/target/package
- let temp_package_dir = workspace_root.join(".temp/target/package");
- std::fs::create_dir_all(&temp_package_dir)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", temp_package_dir.display()));
-
- // cargo package puts .crate files in target/package
- let pre_release_target_package = pre_release_dir.join(".temp/target/package");
- if pre_release_target_package.exists() {
- println_cargo_style!("Copy: packages to .temp/target/package");
- copy_dir_contents(&pre_release_target_package, &temp_package_dir);
- } else {
- wprintln_cargo_style!("No packages found in .temp/pre-release/.temp/target/package");
- }
-
- // 8. Export each crate as a standalone project from the .crate packages.
- // The .crate files contain the final publish-ready Cargo.toml with all
- // workspace/path deps already resolved by `cargo package`.
- let release_dir = workspace_root.join(".temp/release");
- println_cargo_style!("Export: standalone crates to .temp/release/");
- let _ = std::fs::remove_dir_all(&release_dir);
- std::fs::create_dir_all(&release_dir)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", release_dir.display()));
-
- for entry in std::fs::read_dir(&temp_package_dir).expect("failed to read target/package") {
- let entry = entry.expect("failed to read entry");
- let path = entry.path();
- if path.extension().is_none_or(|e| e != "crate") {
- continue;
- }
-
- // .crate files are gzipped tarballs. Extract to .temp/release/<name>/
- // fname is like "mingling-0.3.0"
- let fname = path.file_stem().unwrap().to_string_lossy().to_string();
-
- // Derive crate directory name by stripping the version suffix
- // mingling-0.3.0 -> mingling, arg-picker-0.1.0 -> arg-picker
- let crate_dir_name = fname
- .rfind('-')
- .and_then(|dash| {
- // Check if what follows looks like a semver
- let ver_part = &fname[dash + 1..];
- if ver_part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
- Some(&fname[..dash])
- } else {
- None
- }
- })
- .unwrap_or(&fname)
- .to_string();
-
- let target_dir = release_dir.join(&crate_dir_name);
- std::fs::create_dir_all(&target_dir)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", target_dir.display()));
-
- // Extract using flate2 + tar (cross-platform)
- let file = match std::fs::File::open(&path) {
- Ok(f) => f,
- Err(e) => {
- eprintln_cargo_style!("Failed to open {}: {e}", path.display());
- continue;
- }
- };
- let decoder = GzDecoder::new(file);
- let mut archive = Archive::new(decoder);
- if let Err(e) = archive.unpack(&target_dir) {
- eprintln_cargo_style!("Failed to extract {}: {e}", fname);
- continue;
- }
-
- // Move the contents from the inner dir up one level
- // .crate contains a single top-level dir named after the package
- let inner = target_dir.join(&fname);
- if inner.exists() {
- for inner_entry in std::fs::read_dir(&inner).expect("failed to read inner dir") {
- let inner_entry = inner_entry.expect("failed to read entry");
- let inner_path = inner_entry.path();
- let dest = target_dir.join(inner_path.file_name().unwrap());
- if dest.exists() {
- let _ = std::fs::remove_dir_all(&dest);
- }
- std::fs::rename(&inner_path, &dest).unwrap_or_else(|e| {
- panic!(
- "failed to rename {} -> {}: {e}",
- inner_path.display(),
- dest.display()
- )
- });
- }
- let _ = std::fs::remove_dir_all(&inner);
- }
-
- // Clean up: remove .orig file (only the normalized Cargo.toml is needed)
- let _ = std::fs::remove_file(target_dir.join("Cargo.toml.orig"));
- // Also remove Cargo.lock — standalone crate doesn't need it for publish
- let _ = std::fs::remove_file(target_dir.join("Cargo.lock"));
-
- // Append an empty [workspace] section so each crate is a valid workspace root
- let cargo_toml_path = target_dir.join("Cargo.toml");
- if cargo_toml_path.exists() {
- let mut cargo_content = std::fs::read_to_string(&cargo_toml_path)
- .unwrap_or_else(|e| panic!("failed to read {}: {e}", cargo_toml_path.display()));
- // Only append if there isn't already a [workspace] section
- if !cargo_content.contains("\n[workspace]\n")
- && !cargo_content.ends_with("\n[workspace]\n")
- {
- cargo_content.push_str("\n[workspace]\n");
- std::fs::write(&cargo_toml_path, &cargo_content).unwrap_or_else(|e| {
- panic!("failed to write {}: {e}", cargo_toml_path.display())
- });
- }
- }
-
- println_cargo_style!("Export: {}", crate_dir_name);
- }
-
- println_cargo_style!("Done: .temp/release/ is ready");
-
- // If package failed, report it
- if package_ok.is_err() {
- eprintln_cargo_style!("cargo package reported errors above");
- std::process::exit(1);
- }
-}
-
-/// Parse `[workspace.package]` and `[workspace.dependencies]` from the root Cargo.toml.
-/// Returns (package_fields, dep_values).
-fn parse_workspace_config(
- content: &str,
-) -> (HashMap<String, String>, HashMap<String, toml::Value>) {
- let table: TomlTable = content.parse().expect("failed to parse root Cargo.toml");
-
- let mut package_fields = HashMap::new();
- if let Some(workspace) = table.get("workspace").and_then(|w| w.as_table())
- && let Some(pkg_table) = workspace.get("package").and_then(|p| p.as_table())
- {
- for (k, v) in pkg_table {
- if let Some(s) = v.as_str() {
- package_fields.insert(k.clone(), s.to_string());
- }
- }
- }
-
- let mut dep_values = HashMap::new();
- if let Some(workspace) = table.get("workspace").and_then(|w| w.as_table())
- && let Some(deps_table) = workspace.get("dependencies").and_then(|d| d.as_table())
- {
- for (k, v) in deps_table {
- dep_values.insert(k.clone(), v.clone());
- }
- }
-
- (package_fields, dep_values)
-}
-
-/// Serialize a `toml::Value` into Cargo-toml-compatible inline representation.
-fn toml_value_str(v: &toml::Value) -> String {
- match v {
- toml::Value::String(s) => format!("\"{}\"", s),
- toml::Value::Table(t) => {
- let items: Vec<String> = t
- .iter()
- .map(|(k, val)| format!("{} = {}", k, toml_value_str(val)))
- .collect();
- format!("{{ {} }}", items.join(", "))
- }
- toml::Value::Array(a) => {
- let items: Vec<String> = a.iter().map(toml_value_str).collect();
- format!("[{}]", items.join(", "))
- }
- toml::Value::Boolean(b) => b.to_string(),
- toml::Value::Integer(i) => i.to_string(),
- toml::Value::Float(f) => f.to_string(),
- toml::Value::Datetime(dt) => format!("\"{}\"", dt),
- }
-}
-
-/// Compute the relative path from `member_rel_dir` to `target_path`.
-/// Both are relative to workspace root.
-/// e.g. member_rel_dir="mingling", target_path="mingling_core" → "../mingling_core"
-fn make_path_relative_to_member(target_path: &str, member_rel_dir: &Path) -> String {
- if member_rel_dir.as_os_str().is_empty() || member_rel_dir == Path::new(".") {
- return target_path.to_string();
- }
- let depth = member_rel_dir.components().count();
- let mut result = PathBuf::new();
- for _ in 0..depth {
- result.push("..");
- }
- result.push(target_path);
- result.to_string_lossy().to_string()
-}
-
-/// Resolve a dependency definition from `[workspace.dependencies]` to an inline string.
-/// If the definition contains a path to a workspace member, add `version = "..."`
-/// and adjust the path to be relative to the member's directory.
-fn resolve_dep_def(
- dep_name: &str,
- dep_def: &toml::Value,
- member_rel_dir: &Path,
- version_map: &HashMap<String, String>,
-) -> String {
- match dep_def {
- toml::Value::String(ver) => {
- format!("\"{}\"", ver)
- }
- toml::Value::Table(t) => {
- let mut resolved = t.clone();
- let has_path = t.contains_key("path");
- let is_ws_member = version_map.contains_key(dep_name);
-
- // Fix path to be relative to member's directory
- if has_path && let Some(path_val) = t.get("path").and_then(|v| v.as_str()) {
- let rel = make_path_relative_to_member(path_val, member_rel_dir);
- resolved.insert("path".to_string(), toml::Value::String(rel));
- }
-
- // Add version for workspace member path deps
- if has_path
- && is_ws_member
- && let Some(version) = version_map.get(dep_name)
- {
- resolved.insert("version".to_string(), toml::Value::String(version.clone()));
- }
-
- let items: Vec<String> = resolved
- .iter()
- .map(|(k, val)| format!("{} = {}", k, toml_value_str(val)))
- .collect();
- format!("{{ {} }}", items.join(", "))
- }
- _ => toml_value_str(dep_def),
- }
-}
-
-/// Merge an inline `{ workspace = true, optional = true, ... }` with the workspace definition.
-/// Returns the full resolved dependency value string (without the leading `dep_name = `).
-fn merge_inline_dep(
- inline_rest: &str,
- dep_name: &str,
- dep_def: &toml::Value,
- member_rel_dir: &Path,
- version_map: &HashMap<String, String>,
-) -> String {
- // inline_rest is the part after `=`: `{ workspace = true, optional = true }`
- let inner = inline_rest
- .trim()
- .strip_prefix('{')
- .and_then(|s| s.strip_suffix('}'))
- .unwrap_or("");
-
- match dep_def {
- toml::Value::String(ver) => {
- // Workspace def is just a version string
- // Collect extras: everything except `workspace = true`
- let extras: Vec<&str> = inner
- .split(',')
- .map(|s| s.trim())
- .filter(|s| !s.is_empty() && *s != "workspace = true")
- .collect();
-
- if extras.is_empty() {
- format!("\"{}\"", ver)
- } else {
- // Serialize as inline table: version + extras
- let mut parts = vec![format!("version = \"{}\"", ver)];
- parts.extend(extras.iter().map(|s| s.to_string()));
- format!("{{ {} }}", parts.join(", "))
- }
- }
- toml::Value::Table(t) => {
- // Start from workspace def
- let mut merged = t.clone();
-
- // Fix path to be relative to member's directory
- if let Some(path_val) = t.get("path").and_then(|v| v.as_str()) {
- let rel = make_path_relative_to_member(path_val, member_rel_dir);
- merged.insert("path".to_string(), toml::Value::String(rel));
- }
-
- // If this dep is a workspace member with a path dep, add version
- if t.contains_key("path")
- && version_map.contains_key(dep_name)
- && let Some(version) = version_map.get(dep_name)
- {
- merged.insert("version".to_string(), toml::Value::String(version.clone()));
- }
-
- // Apply extra fields from the inline
- for piece in inner.split(',').map(|s| s.trim()) {
- let piece = piece.trim();
- if piece.is_empty() || piece == "workspace = true" {
- continue;
- }
- // Parse `key = value` pairs
- if let Some((raw_key, raw_val)) = piece.split_once('=') {
- let k = raw_key.trim();
- let v = raw_val.trim();
- if k.is_empty() {
- continue;
- }
- // Try to infer the value type
- if v == "true" {
- merged.insert(k.to_string(), toml::Value::Boolean(true));
- } else if v == "false" {
- merged.insert(k.to_string(), toml::Value::Boolean(false));
- } else if v.starts_with('"') && v.ends_with('"') {
- merged.insert(
- k.to_string(),
- toml::Value::String(v[1..v.len() - 1].to_string()),
- );
- } else if v.starts_with('[') && v.ends_with(']') {
- // Simple array parsing: strings only
- let arr: Vec<toml::Value> = v[1..v.len() - 1]
- .split(',')
- .map(|s| {
- let s = s.trim().trim_matches('"');
- toml::Value::String(s.to_string())
- })
- .collect();
- merged.insert(k.to_string(), toml::Value::Array(arr));
- } else if let Ok(n) = v.parse::<i64>() {
- merged.insert(k.to_string(), toml::Value::Integer(n));
- } else if let Ok(f) = v.parse::<f64>() {
- merged.insert(k.to_string(), toml::Value::Float(f));
- } else {
- // Treat as string
- merged.insert(k.to_string(), toml::Value::String(v.to_string()));
- }
- }
- }
-
- let items: Vec<String> = merged
- .iter()
- .map(|(k, val)| format!("{} = {}", k, toml_value_str(val)))
- .collect();
- format!("{{ {} }}", items.join(", "))
- }
- _ => toml_value_str(dep_def),
- }
-}
-
-/// Resolve ALL workspace inheritance in a single member crate's Cargo.toml:
-/// - `version.workspace = true` → `version = "0.3.0"`
-/// - `dep.workspace = true` → inline the full definition from ws_deps
-/// - `dep = { workspace = true, ... }` → merge with ws_deps definition
-fn resolve_member_manifest(
- content: &str,
- member_rel_dir: &Path,
- ws_package: &HashMap<String, String>,
- ws_deps: &HashMap<String, toml::Value>,
- version_map: &HashMap<String, String>,
-) -> String {
- let mut result = String::new();
- let mut in_package = false;
- let mut in_section_with_deps = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect();
-
- // Track sections
- if trimmed.starts_with('[') {
- in_package = trimmed == "[package]";
- in_section_with_deps = trimmed.starts_with("[dependencies")
- || trimmed.starts_with("[build-dependencies")
- || trimmed.starts_with("[dev-dependencies");
- result.push_str(line);
- result.push('\n');
- continue;
- }
-
- // [package] section: resolve `field.workspace = true`
- if in_package && trimmed.ends_with(".workspace = true") {
- let key = trimmed.strip_suffix(".workspace = true").unwrap().trim();
- if let Some(value) = ws_package.get(key) {
- result.push_str(&format!("{indent}{key} = \"{value}\"\n"));
- continue;
- }
- // Also check workspace.dependencies (for fields like `version.workspace`)
- // when the member has its own version field inherited from workspace.package
- }
-
- // Dependency sections
- if in_section_with_deps {
- // Shorthand: `foo.workspace = true`
- if trimmed.ends_with(".workspace = true") {
- let key = trimmed.strip_suffix(".workspace = true").unwrap().trim();
- if let Some(dep_def) = ws_deps.get(key) {
- let resolved = resolve_dep_def(key, dep_def, member_rel_dir, version_map);
- result.push_str(&format!("{indent}{key} = {resolved}\n"));
- continue;
- }
- }
-
- // Inline: `foo = { workspace = true, optional = true, ... }`
- if let Some(eq_pos) = trimmed.find("= {")
- && trimmed.contains("workspace = true")
- {
- let dep_name = trimmed[..eq_pos].trim();
- if let Some(dep_def) = ws_deps.get(dep_name) {
- let after_eq = trimmed[eq_pos + 1..].trim();
- let merged =
- merge_inline_dep(after_eq, dep_name, dep_def, member_rel_dir, version_map);
- result.push_str(&format!("{indent}{dep_name} = {merged}\n"));
- continue;
- }
- }
- }
-
- result.push_str(line);
- result.push('\n');
- }
-
- result
-}
-
-/// Remove `[workspace.dependencies]`, `[workspace.package]`, and the root `[package]`
-/// section from root Cargo.toml, making it a pure virtual manifest.
-/// Keeps `[workspace]` with `members`, `resolver`, `exclude` so packaging still works.
-fn strip_workspace_config(content: &str) -> String {
- let mut result = String::new();
- let mut in_ws_deps = false;
- let mut in_ws_package = false;
- let mut in_root_package = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
-
- if trimmed == "[workspace.dependencies]" {
- in_ws_deps = true;
- continue;
- }
- if trimmed == "[workspace.package]" {
- in_ws_package = true;
- continue;
- }
- if trimmed == "[package]" && !in_ws_deps && !in_ws_package {
- // Remove the root [package] section entirely (virtual manifest)
- in_root_package = true;
- continue;
- }
-
- if in_ws_deps {
- if trimmed.starts_with('[') {
- in_ws_deps = false;
- } else {
- continue;
- }
- }
-
- if in_ws_package {
- if trimmed.starts_with('[') {
- in_ws_package = false;
- } else {
- continue;
- }
- }
-
- if in_root_package {
- if trimmed.starts_with('[') {
- in_root_package = false;
- } else {
- continue;
- }
- }
-
- result.push_str(line);
- result.push('\n');
- }
-
- result
-}
-
-/// Recursively copy a directory.
-fn copy_dir(src: &Path, dst: &Path) {
- copy_dir_filtered(src, dst, &|_: &Path| true)
-}
-
-/// Recursively copy a directory with a filter function.
-/// The filter receives the source path and returns `true` if the entry should be copied.
-fn copy_dir_filtered(src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {
- if !src.exists() {
- return;
- }
- if !filter(src) {
- return;
- }
- std::fs::create_dir_all(dst)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", dst.display()));
-
- for entry in std::fs::read_dir(src).expect("failed to read directory") {
- let entry = entry.expect("failed to read entry");
- let entry_type = entry.file_type().expect("failed to get file type");
- let src_path = entry.path();
- let dst_path = dst.join(entry.file_name());
-
- if !filter(&src_path) {
- continue;
- }
-
- if entry_type.is_dir() {
- copy_dir_filtered(&src_path, &dst_path, filter);
- } else if entry_type.is_file() || entry_type.is_symlink() {
- copy_file(&src_path, &dst_path);
- }
- }
-}
-
-/// Copy a file, creating parent directories as needed.
-/// If src is a symlink, copies the target content (follow symlinks).
-fn copy_file(src: &Path, dst: &Path) {
- if let Some(parent) = dst.parent() {
- std::fs::create_dir_all(parent)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", parent.display()));
- }
-
- let resolved = if src.is_symlink() {
- let target = std::fs::read_link(src)
- .unwrap_or_else(|e| panic!("failed to read symlink {}: {e}", src.display()));
- if target.is_relative() {
- src.parent().unwrap().join(target)
- } else {
- target
- }
- } else {
- src.to_path_buf()
- };
-
- std::fs::copy(&resolved, dst).unwrap_or_else(|e| {
- panic!(
- "failed to copy {} -> {}: {e}",
- resolved.display(),
- dst.display()
- )
- });
-}
-
-/// Copy all files/directories from one directory into another.
-fn copy_dir_contents(src: &Path, dst: &Path) {
- if !src.exists() {
- return;
- }
- std::fs::create_dir_all(dst)
- .unwrap_or_else(|e| panic!("failed to create {}: {e}", dst.display()));
-
- for entry in std::fs::read_dir(src).expect("failed to read directory") {
- let entry = entry.expect("failed to read entry");
- let entry_type = entry.file_type().expect("failed to get file type");
- let src_path = entry.path();
- let dst_path = dst.join(entry.file_name());
-
- if entry_type.is_dir() {
- copy_dir(&src_path, &dst_path);
- } else if entry_type.is_file() || entry_type.is_symlink() {
- copy_file(&src_path, &dst_path);
- }
- }
-}
diff --git a/.run/src/bin/update-version.rs b/.run/src/bin/update-version.rs
deleted file mode 100644
index 2283662..0000000
--- a/.run/src/bin/update-version.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use std::io::Write as _;
-use std::path::Path;
-
-use serde::Deserialize;
-use tools::println_cargo_style;
-
-#[derive(Deserialize)]
-struct VersionFile {
- file: String,
- pattern: String,
-}
-
-#[derive(Deserialize)]
-struct Config {
- #[serde(rename = "file")]
- files: Vec<VersionFile>,
-}
-
-fn main() {
- let args: Vec<String> = std::env::args().collect();
-
- // Get new version
- let new_ver = if args.len() > 1 {
- args[1].clone()
- } else {
- print!("Update version to: ");
- std::io::stdout().flush().unwrap();
- let mut input = String::new();
- std::io::stdin().read_line(&mut input).unwrap();
- input.trim().to_string()
- };
-
- if new_ver.is_empty() {
- eprintln!("Error: Version cannot be empty.");
- std::process::exit(1);
- }
-
- // Read current version from root Cargo.toml's workspace.package.version
- let root_cargo_path = "Cargo.toml";
- let root_cargo_content =
- std::fs::read_to_string(root_cargo_path).expect("Failed to read Cargo.toml");
- let cargo_value: toml::Value = root_cargo_content
- .parse()
- .expect("Failed to parse Cargo.toml");
-
- let current_ver = cargo_value["workspace"]["package"]["version"]
- .as_str()
- .expect("workspace.package.version not found in Cargo.toml")
- .to_string();
-
- if new_ver == current_ver {
- println!("Version is already {}. Nothing to do.", current_ver);
- return;
- }
-
- println_cargo_style!("Version: {} -> {}", current_ver, new_ver);
-
- // Read version-files.toml
- let config_path = Path::new(".config").join("version-files.toml");
- let config_str =
- std::fs::read_to_string(&config_path).expect("Failed to read .config/version-files.toml");
- let config: Config =
- toml::from_str(&config_str).expect("Failed to parse .config/version-files.toml");
-
- let mut updated_count = 0;
- let mut skipped_count = 0;
-
- for vf in &config.files {
- let file_path = &vf.file;
- let old_pattern = vf.pattern.replace("{VER}", &current_ver);
- let new_pattern = vf.pattern.replace("{VER}", &new_ver);
-
- let content = match std::fs::read_to_string(file_path) {
- Ok(c) => c,
- Err(e) => {
- eprintln!("Warning: Could not read {}: {}", file_path, e);
- skipped_count += 1;
- continue;
- }
- };
-
- let new_content = content.replace(&old_pattern, &new_pattern);
-
- if new_content == content {
- eprintln!(
- "Warning: Pattern '{}' not found in {}",
- old_pattern, file_path
- );
- skipped_count += 1;
- continue;
- }
-
- std::fs::write(file_path, &new_content)
- .unwrap_or_else(|e| panic!("Failed to write {}: {}", file_path, e));
- println_cargo_style!("Updated: {}", file_path);
- updated_count += 1;
- }
-
- println_cargo_style!(
- "Done: {} file(s) updated, {} file(s) skipped",
- updated_count,
- skipped_count
- );
-}
diff --git a/.run/src/bin/windows-folder-hide.ps1 b/.run/src/bin/windows-folder-hide.ps1
deleted file mode 100644
index ff53202..0000000
--- a/.run/src/bin/windows-folder-hide.ps1
+++ /dev/null
@@ -1,115 +0,0 @@
-$skipDirs = @('.git', '.temp', 'target', 'node_modules', '.pnpm')
-$selfPath = (Get-Item -LiteralPath $MyInvocation.MyCommand.Path).Directory.FullName
-
-function Test-InSkipDir {
- param(
- [object]$Item
- )
- $path = if ($Item -is [string]) {
- $Item
- } elseif ($Item.PSPath) {
- $Item.PSPath -replace '^.*::', ''
- } else {
- $Item.FullName
- }
-
- $parts = $path.Split([System.IO.Path]::DirectorySeparatorChar)
- for ($i = 0; $i -lt $parts.Length - 1; $i++) {
- if ($parts[$i] -in $skipDirs) {
- return $true
- }
- }
- return $false
-}
-
-function Invoke-UnhideRecursive {
- param([string]$Path)
- Get-ChildItem -LiteralPath $Path -Force | ForEach-Object {
- if ($_.PSIsContainer) {
- if ($_.Name -in $skipDirs) {
- if ($_.Attributes -band [System.IO.FileAttributes]::Hidden) {
- Write-Host " -> unhiding skip directory (self only): `"$($_.FullName)`""
- $_.Attributes = $_.Attributes -bxor [System.IO.FileAttributes]::Hidden
- }
- return
- }
- Invoke-UnhideRecursive $_.FullName
- } else {
- if ($_.Attributes -band [System.IO.FileAttributes]::Hidden) {
- Write-Host " -> unhiding: `"$($_.FullName)`""
- $_.Attributes = $_.Attributes -bxor [System.IO.FileAttributes]::Hidden
- }
- }
- }
-}
-
-function Test-GitPathSkippable {
- param([string]$GitPath)
- $parts = $GitPath.Split(@('/', '\'))
- for ($i = 0; $i -lt $parts.Length - 1; $i++) {
- if ($parts[$i] -in $skipDirs) {
- return $true
- }
- }
- return $false
-}
-
-Write-Host "Step 1: Unhiding all files and directories (skipping $($skipDirs -join ', '))..."
-
-Invoke-UnhideRecursive -Path (Get-Location).Path
-
-Write-Host "Step 2: Hiding git-ignored items..."
-
-git ls-files --others --ignored --exclude-standard | Where-Object {
- -not (Test-GitPathSkippable $_)
-} | ForEach-Object {
- $itemPath = $_
- Write-Host "... checking: `"$itemPath`""
- $item = Get-Item $_ -Force -ErrorAction SilentlyContinue
- if (-not $item) { return }
-
- if ($item.FullName -eq $selfPath) { return }
-
- if (Test-InSkipDir $item) {
- Write-Host " -> skipping (inside skip directory)"
- return
- }
-
- if ($item.PSIsContainer) {
- if (-not ($item.Attributes -band [System.IO.FileAttributes]::Hidden)) {
- Write-Host " -> hiding directory (non-recursive)"
- $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
- }
- } else {
- if (-not ($item.Attributes -band [System.IO.FileAttributes]::Hidden)) {
- Write-Host " -> hiding"
- $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
- }
- }
-}
-
-Write-Host "Step 3: Hiding dot-prefixed items..."
-Get-ChildItem -Path . -Force -Directory | Where-Object { $_.Name -match '^\.' } | ForEach-Object {
- Write-Host "... checking: `"$($_.FullName)`""
- if (Test-InSkipDir $_) {
- Write-Host " -> skipping (inside skip directory)"
- return
- }
- if (-not ($_.Attributes -band [System.IO.FileAttributes]::Hidden)) {
- Write-Host " -> hiding directory"
- $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden
- }
-}
-
-Get-ChildItem -Path . -Force -File | Where-Object { $_.Name -match '^\.' } | ForEach-Object {
- if ($_.FullName -eq $selfPath) { return }
- Write-Host "... checking: `"$($_.FullName)`""
- if (Test-InSkipDir $_) {
- Write-Host " -> skipping (inside skip directory)"
- return
- }
- if (-not ($_.Attributes -band [System.IO.FileAttributes]::Hidden)) {
- Write-Host " -> hiding file"
- $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden
- }
-}