aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.run/src/bin/RELEASE-WORKSPACE.rs145
-rw-r--r--.run/src/bin/package-all.rs506
-rw-r--r--arg_picker/test/src/test/value_flag_test.rs10
-rw-r--r--docs/dev/pages/abouts/ci.md35
-rw-r--r--mingling/src/lib.md5
6 files changed, 537 insertions, 167 deletions
diff --git a/.gitignore b/.gitignore
index dc2fd0b..274c6cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,3 @@ __*/
# Fuck
nul
-
-# Release script
-Cargo.toml.bkup
diff --git a/.run/src/bin/RELEASE-WORKSPACE.rs b/.run/src/bin/RELEASE-WORKSPACE.rs
deleted file mode 100644
index 8cb4d1b..0000000
--- a/.run/src/bin/RELEASE-WORKSPACE.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::path::{Path, PathBuf};
-use std::process;
-use tools::dependency_order::{display_dependency_order, find_workspace_root};
-use tools::{
- eprintln_cargo_style, println_cargo_style, run_cmd_capture_with_dir, run_cmd_with_dir,
-};
-
-/// Read the version from `[workspace.package].version` in the root Cargo.toml.
-fn read_workspace_version(workspace_root: &Path) -> Option<String> {
- let content = std::fs::read_to_string(workspace_root.join("Cargo.toml")).ok()?;
- let value: toml::Value = content.parse().ok()?;
- value
- .get("workspace")?
- .get("package")?
- .get("version")?
- .as_str()
- .map(String::from)
-}
-
-/// Replace `path = "crate_name"` with `version = "VERSION"` in the file content.
-fn update_path_to_version(content: &str, crate_name: &str, version: &str) -> String {
- let old = format!("path = \"{}\"", crate_name);
- let new = format!("version = \"{}\"", version);
- content.replace(&old, &new)
-}
-
-/// Restore the backup and abort.
-fn abort(toml_path: &Path) -> ! {
- let backup_path = toml_path.with_extension("toml.bkup");
- if backup_path.exists() {
- if let Err(e) = std::fs::copy(&backup_path, toml_path) {
- eprintln_cargo_style!("failed to restore Cargo.toml from backup: {}", e);
- } else {
- eprintln_cargo_style!("restored Cargo.toml from backup");
- }
- }
- process::exit(1);
-}
-
-fn main() {
- // 1. Compute dependency order
- let order = display_dependency_order();
- if order.is_empty() {
- eprintln_cargo_style!("could not find workspace root or mingling crates");
- process::exit(1);
- }
-
- // 2. Locate workspace root
- let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
- let workspace_root = match find_workspace_root(&cwd) {
- Some(r) => r,
- None => {
- eprintln_cargo_style!("could not find workspace root");
- process::exit(1);
- }
- };
-
- // 3. Read current version
- let version = match read_workspace_version(&workspace_root) {
- Some(v) => v,
- None => {
- eprintln_cargo_style!("could not read workspace.package.version from Cargo.toml");
- process::exit(1);
- }
- };
-
- println_cargo_style!("Version: detected version {}", version);
-
- let toml_path = workspace_root.join("Cargo.toml");
- let backup_path = workspace_root.join("Cargo.toml.bkup");
-
- // 4. Backup Cargo.toml
- if let Err(e) = std::fs::copy(&toml_path, &backup_path) {
- eprintln_cargo_style!("failed to backup Cargo.toml: {}", e);
- process::exit(1);
- }
- println_cargo_style!("Backup: created Cargo.toml.bkup");
-
- // 5. Iterate crates in dependency order
- for crate_name in &order {
- let name = match crate_name.to_str() {
- Some(n) => n,
- None => {
- eprintln_cargo_style!("invalid crate path: {}", crate_name.display());
- abort(&toml_path);
- }
- };
-
- let crate_dir = workspace_root.join(crate_name);
-
- // 5a. Publish (capture output to check for warnings)
- println_cargo_style!("Publish: {}", name);
- let output = run_cmd_capture_with_dir("cargo publish", &crate_dir);
- let output_str = match output {
- Ok(o) => o,
- Err((code, _)) => {
- eprintln_cargo_style!("{} publish failed (exit code {})", name, code);
- abort(&toml_path);
- }
- };
-
- // Reject any warnings
- let warnings: Vec<&str> = output_str
- .lines()
- .filter(|l| l.to_lowercase().contains("warning:"))
- .collect();
- if !warnings.is_empty() {
- for w in &warnings {
- eprintln_cargo_style!("warning detected: {}", w);
- }
- eprintln_cargo_style!(
- "{} publish rejected due to {} warning(s)",
- name,
- warnings.len()
- );
- abort(&toml_path);
- }
-
- // 5b. Replace path with version in Cargo.toml
- let content = match std::fs::read_to_string(&toml_path) {
- Ok(c) => c,
- Err(e) => {
- eprintln_cargo_style!("failed to read Cargo.toml: {}", e);
- abort(&toml_path);
- }
- };
- let new_content = update_path_to_version(&content, name, &version);
- if let Err(e) = std::fs::write(&toml_path, &new_content) {
- eprintln_cargo_style!("failed to write Cargo.toml: {}", e);
- abort(&toml_path);
- }
- println_cargo_style!("Dependency: {} path -> version \"{}\"", name, version);
-
- // 5c. Verify workspace compiles
- println_cargo_style!("Check: workspace");
- if let Err(code) = run_cmd_with_dir("cargo check --workspace", &workspace_root) {
- eprintln_cargo_style!("cargo check --workspace failed (exit code {})", code);
- abort(&toml_path);
- }
-
- println_cargo_style!("Done: `{}` published", name);
- }
-
- println_cargo_style!("Done: all crates published successfully");
-}
diff --git a/.run/src/bin/package-all.rs b/.run/src/bin/package-all.rs
new file mode 100644
index 0000000..59f0f89
--- /dev/null
+++ b/.run/src/bin/package-all.rs
@@ -0,0 +1,506 @@
+use std::path::{Path, PathBuf};
+
+use serde::Deserialize;
+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!("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: std::collections::HashMap<String, String> =
+ std::collections::HashMap::new();
+ for m in &members {
+ version_map.insert(m.name.clone(), m.version.clone());
+ }
+
+ // Collect unique member directories that need to be copied
+ // Compute relative path by stripping workspace_root prefix
+ 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 .cargo directory
+ copy_dir(
+ &workspace_root.join(".cargo"),
+ &pre_release_dir.join(".cargo"),
+ );
+
+ // Copy each member directory
+ for dir in &member_dirs {
+ let src = workspace_root.join(dir);
+ let dst = pre_release_dir.join(dir);
+ copy_dir(&src, &dst);
+ }
+
+ // Copy root Cargo.toml and Cargo.lock
+ 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. Replace workspace dependency paths with version numbers in the root Cargo.toml
+ // For workspace-member crates, keep path so cargo can resolve locally during
+ // `cargo package --workspace`. `cargo package` automatically converts path deps
+ // to version deps in the final .crate manifest.
+ println_cargo_style!("Patch: resolve workspace dependency versions");
+
+ let pre_release_cargo = pre_release_dir.join("Cargo.toml");
+ let content = std::fs::read_to_string(&pre_release_cargo)
+ .unwrap_or_else(|e| panic!("failed to read {}: {e}", pre_release_cargo.display()));
+
+ let patched = patch_workspace_deps(&content, &version_map);
+
+ std::fs::write(&pre_release_cargo, &patched)
+ .unwrap_or_else(|e| panic!("failed to write {}: {e}", pre_release_cargo.display()));
+
+ // Member cargo.toml files: replace direct `path = "..."` deps (pointing to other
+ // workspace members) with version qualification, so `cargo package` can produce
+ // a valid .crate without path dependencies.
+ 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 member_patched = patch_member_path_deps(&member_content, &version_map);
+ if member_patched != member_content {
+ std::fs::write(&member_cargo, &member_patched)
+ .unwrap_or_else(|e| panic!("failed to write {}: {e}", member_cargo.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!("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().map_or(true, |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()
+ .map_or(false, |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 tar + gzip
+ let extract_cmd = format!(
+ "tar -xzf \"{}\" -C \"{}\"",
+ path.display(),
+ target_dir.display()
+ );
+ let extract_ok = std::process::Command::new("sh")
+ .arg("-c")
+ .arg(&extract_cmd)
+ .status()
+ .expect("failed to run tar");
+
+ if !extract_ok.success() {
+ eprintln_cargo_style!("Failed to extract {}", 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"));
+
+ 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);
+ }
+}
+
+/// Replace path-based workspace dependencies with version strings.
+///
+/// Keeps the path form so that `cargo package --workspace` resolves to local workspace
+/// members, but also adds `version = "..."` so the generated .crate has the correct
+/// version dependency.
+fn patch_workspace_deps(
+ content: &str,
+ version_map: &std::collections::HashMap<String, String>,
+) -> String {
+ let mut result = String::new();
+ let mut in_workspace_deps = false;
+
+ for line in content.lines() {
+ let trimmed = line.trim();
+
+ if trimmed == "[workspace.dependencies]" {
+ in_workspace_deps = true;
+ result.push_str(line);
+ result.push('\n');
+ continue;
+ }
+
+ // Detect end of workspace.dependencies section
+ if in_workspace_deps && trimmed.starts_with('[') {
+ in_workspace_deps = false;
+ }
+
+ if in_workspace_deps {
+ if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
+ if let Some(version) = version_map.get(dep_name) {
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+
+ if trimmed.contains("path =") {
+ let path_value = extract_path_value(trimmed);
+ let patched_line: String = if let Some(pv) = path_value {
+ trimmed.replace(
+ &format!("path = \"{}\"", pv),
+ &format!("path = \"{}\", version = \"{}\"", pv, version),
+ )
+ } else {
+ trimmed.to_string()
+ };
+ result.push_str(&format!("{indent}{patched_line}\n"));
+ } else {
+ result.push_str(&format!("{indent}{dep_name} = \"{version}\"\n"));
+ }
+ continue;
+ }
+ }
+ }
+
+ result.push_str(line);
+ result.push('\n');
+ }
+
+ result
+}
+
+/// Extract the path value from a dependency line like:
+/// `mingling_core = { path = "mingling_core", default-features = false }`
+fn extract_path_value(line: &str) -> Option<String> {
+ let line = line.trim();
+ if let Some(start) = line.find("path = \"") {
+ let after_path = &line[start + 8..];
+ if let Some(end) = after_path.find('"') {
+ return Some(after_path[..end].to_string());
+ }
+ }
+ None
+}
+
+/// Patch a member crate's Cargo.toml: add `version = "..."` to direct `path = "..."`
+/// dependencies that point to other workspace members.
+fn patch_member_path_deps(
+ content: &str,
+ version_map: &std::collections::HashMap<String, String>,
+) -> String {
+ let mut result = String::new();
+ let mut in_deps = false;
+ let mut in_build_deps = false;
+
+ for line in content.lines() {
+ let trimmed = line.trim();
+
+ if trimmed == "[dependencies]" || trimmed.starts_with("[dependencies.") {
+ in_deps = true;
+ in_build_deps = false;
+ } else if trimmed == "[build-dependencies]" || trimmed.starts_with("[build-dependencies.") {
+ in_build_deps = true;
+ in_deps = false;
+ } else if trimmed.starts_with('[') {
+ in_deps = false;
+ in_build_deps = false;
+ }
+
+ if (in_deps || in_build_deps)
+ && trimmed.contains("path = \"")
+ && !trimmed.contains("workspace = true")
+ {
+ if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
+ let dep_name = dep_name.trim_end_matches(".workspace");
+ if let Some(version) = version_map.get(dep_name) {
+ if !trimmed.contains("version = \"") {
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+ let path_val = extract_path_value(trimmed).unwrap_or_default();
+ let patched = trimmed.replace(
+ &format!("path = \"{path_val}\""),
+ &format!("path = \"{path_val}\", version = \"{version}\""),
+ );
+ result.push_str(&format!("{indent}{patched}\n"));
+ 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/arg_picker/test/src/test/value_flag_test.rs b/arg_picker/test/src/test/value_flag_test.rs
index 3df053e..c60ab1e 100644
--- a/arg_picker/test/src/test/value_flag_test.rs
+++ b/arg_picker/test/src/test/value_flag_test.rs
@@ -1,5 +1,5 @@
use arg_picker::value::Flag;
-use arg_picker::{macros::arg, IntoPicker};
+use arg_picker::{IntoPicker, macros::arg};
// Basic Flag — present / absent
@@ -136,10 +136,10 @@ fn test_flag_to_option() {
#[test]
fn test_flag_converts_to_bool() {
let flag = Flag::Active;
- assert!(bool::from(flag));
+ assert_eq!(bool::from(flag), true);
let flag = Flag::Inactive;
- assert!(!bool::from(flag));
+ assert_eq!(bool::from(flag), false);
}
#[test]
@@ -151,10 +151,10 @@ fn test_flag_from_bool() {
#[test]
fn test_flag_deref_to_bool() {
let active = Flag::Active;
- assert!(*active);
+ assert_eq!(*active, true);
let inactive = Flag::Inactive;
- assert!(!*inactive);
+ assert_eq!(*inactive, false);
}
// Flag never triggers route (unlike bool)
diff --git a/docs/dev/pages/abouts/ci.md b/docs/dev/pages/abouts/ci.md
index 3a93c1c..8b59b72 100644
--- a/docs/dev/pages/abouts/ci.md
+++ b/docs/dev/pages/abouts/ci.md
@@ -37,11 +37,11 @@ cargo ci
- **Test all examples**: Runs the `test-examples` tool.
- **Verify Markdown code blocks compile**: Runs the `test-all-markdown-code` tool to check code blocks in all `*.md` files. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md) for details.
- **Check if documentation is up to date**: Runs the following documentation refresh tools in sequence:
- - `docs-code-box-fix`
- - `docsify-sidebar-gen`
- - `refresh-docs`
- - `refresh-feature-mod`
- - `sync-examples`
+ - `docs-code-box-fix`
+ - `docsify-sidebar-gen`
+ - `refresh-docs`
+ - `refresh-feature-mod`
+ - `sync-examples`
- Finally, runs `cargo fmt` to unify code formatting.
### 3. File Normalization
@@ -53,11 +53,11 @@ Runs `git add --renormalize .` to ensure file attributes such as line endings co
To ensure reproducible CI results, `ci.rs` imposes strict requirements on the workspace state:
- If the current workspace is not clean and `--dirty` has not been specified, the script will prompt whether to create a temporary commit:
- - The commit message is `[DO NOT PUSH] CI TEMP [DO NOT PUSH]`.
- - Use `-y` to auto-confirm without interaction.
+ - The commit message is `[DO NOT PUSH] CI TEMP [DO NOT PUSH]`.
+ - Use `-y` to auto-confirm without interaction.
- After CI finishes, the script automatically restores the workspace:
- - First, `git reset --hard` discards all changes.
- - If a temporary commit was created, it then runs `git reset --soft HEAD~1` and unstages everything, restoring the state to before CI started.
+ - First, `git reset --hard` discards all changes.
+ - If a temporary commit was created, it then runs `git reset --soft HEAD~1` and unstages everything, restoring the state to before CI started.
- If `--dirty` is specified, the temporary commit and the final cleanliness check are skipped.
> **Warning**: `git reset --hard` is executed at the end of CI. If you use `--dirty`, ensure you have no unsaved important changes.
@@ -69,3 +69,20 @@ To ensure reproducible CI results, `ci.rs` imposes strict requirements on the wo
- Triggered on `push` to the `main` branch.
- Runs `cargo ci` in parallel on `ubuntu-latest` and `windows-latest`.
- After CI passes, the `unreleased` tag is automatically moved to the latest commit on `main`.
+
+### 4. API Documentation Deployment
+
+After all checks pass, the `Deploy-Github-Pages` job:
+
+- **Runs `deploy-api-docs`**: Executes `cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs`, which reads the `[package.metadata.docs.rs]` features from `mingling/Cargo.toml` and builds the crate's documentation via `cargo doc --no-deps`. The output is placed at `docs/api-docs/`.
+- **Deploys to GitHub Pages**: The entire repository (including the generated `docs/api-docs/`) is uploaded and published to GitHub Pages.
+
+You can view the published API documentation at:
+
+> [https://mingling-rs.github.io/mingling/docs/api-docs/mingling/](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/)
+
+To generate API docs locally:
+
+```bash
+cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs
+```
diff --git a/mingling/src/lib.md b/mingling/src/lib.md
index 0fcf811..cd89b96 100644
--- a/mingling/src/lib.md
+++ b/mingling/src/lib.md
@@ -1,8 +1,3 @@
-<p align="center">
- <a href="https://github.com/mingling-rs/mingling">
- <img alt="Mingling" src="https://github.com/mingling-rs/mingling/raw/main/docs/res/icon2.png" width="50%">
- </a>
-</p>
<h1 align="center">Mìng Lìng - 命令</h1>
<p align="center">