summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-12 19:04:12 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-12 19:04:12 +0800
commit72c57380883a1c1cc796dea6d35048ab5bed5f53 (patch)
tree936e04d2ec0f5bae54667beac6bf069208900a80 /macros
parent9d812580557cdc343378816cd65678b8aa75d944 (diff)
Add helpdoc system with interactive viewer
Diffstat (limited to 'macros')
-rw-r--r--macros/helpdoc_system_macros/Cargo.toml12
-rw-r--r--macros/helpdoc_system_macros/src/lib.rs192
2 files changed, 204 insertions, 0 deletions
diff --git a/macros/helpdoc_system_macros/Cargo.toml b/macros/helpdoc_system_macros/Cargo.toml
new file mode 100644
index 0000000..c5c8483
--- /dev/null
+++ b/macros/helpdoc_system_macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "helpdoc_system_macros"
+edition = "2024"
+version.workspace = true
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2.workspace = true
+quote.workspace = true
+syn.workspace = true
diff --git a/macros/helpdoc_system_macros/src/lib.rs b/macros/helpdoc_system_macros/src/lib.rs
new file mode 100644
index 0000000..a9008bf
--- /dev/null
+++ b/macros/helpdoc_system_macros/src/lib.rs
@@ -0,0 +1,192 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use std::fs;
+use std::path::Path;
+use syn;
+
+#[proc_macro]
+pub fn generate_helpdoc_mapping(_input: TokenStream) -> TokenStream {
+ let manifest_dir =
+ std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get CARGO_MANIFEST_DIR");
+ let repo_root = Path::new(&manifest_dir);
+ let helpdoc_dir = repo_root.join("resources").join("helpdoc");
+
+ if !helpdoc_dir.exists() {
+ return quote! {
+ fn get_doc(_doc_name: &str, _lang: &str) -> &'static str {
+ ""
+ }
+ }
+ .into();
+ }
+
+ let mut doc_entries = Vec::new();
+
+ scan_directory(&helpdoc_dir, &mut doc_entries, &helpdoc_dir);
+
+ let match_arms = generate_match_arms(&doc_entries);
+
+ let expanded = quote! {
+ fn get_doc(doc_name: &str, lang: &str) -> &'static str {
+ let key = format!("{}.{}", doc_name, lang);
+ match key.as_str() {
+ #(#match_arms)*
+ _ => "",
+ }
+ }
+ };
+
+ expanded.into()
+}
+
+fn scan_directory(dir: &Path, entries: &mut Vec<(String, String)>, base_dir: &Path) {
+ if let Ok(entries_iter) = fs::read_dir(dir) {
+ for entry in entries_iter.filter_map(Result::ok) {
+ let path = entry.path();
+
+ if path.is_dir() {
+ scan_directory(&path, entries, base_dir);
+ } else if let Some(extension) = path.extension() {
+ if extension == "md" {
+ if let Ok(relative_path) = path.strip_prefix(base_dir) {
+ if let Some(file_stem) = path.file_stem() {
+ let file_stem_str = file_stem.to_string_lossy();
+
+ if let Some(dot_pos) = file_stem_str.rfind('.') {
+ let doc_name = &file_stem_str[..dot_pos];
+ let lang = &file_stem_str[dot_pos + 1..];
+
+ let parent = relative_path.parent();
+ let full_doc_name = if let Some(parent) = parent {
+ if parent.to_string_lossy().is_empty() {
+ doc_name.to_string()
+ } else {
+ format!("{}/{}", parent.to_string_lossy(), doc_name)
+ }
+ } else {
+ doc_name.to_string()
+ };
+
+ entries.push((full_doc_name, lang.to_string()));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn generate_match_arms(entries: &[(String, String)]) -> Vec<proc_macro2::TokenStream> {
+ let mut arms = Vec::new();
+
+ for (doc_name, lang) in entries {
+ let key = format!("{}.{}", doc_name, lang);
+ let file_path = format!("resources/helpdoc/{}.{}.md", doc_name, lang);
+
+ let arm = quote! {
+ #key => include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", #file_path)),
+ };
+
+ arms.push(arm);
+ }
+
+ arms
+}
+
+#[proc_macro]
+pub fn generate_helpdoc_list(_input: TokenStream) -> TokenStream {
+ let manifest_dir =
+ std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get CARGO_MANIFEST_DIR");
+ let repo_root = Path::new(&manifest_dir);
+ let helpdoc_dir = repo_root.join("resources").join("helpdoc");
+
+ if !helpdoc_dir.exists() {
+ return quote! {
+ fn get_docs_list() -> Vec<&'static str> {
+ Vec::new()
+ }
+ }
+ .into();
+ }
+
+ let mut doc_entries = Vec::new();
+ scan_directory(&helpdoc_dir, &mut doc_entries, &helpdoc_dir);
+
+ let mut unique_docs = std::collections::HashSet::new();
+ for (doc_name, _) in &doc_entries {
+ unique_docs.insert(doc_name.clone());
+ }
+
+ let mut doc_list = Vec::new();
+ for doc_name in unique_docs {
+ doc_list.push(quote! {
+ #doc_name
+ });
+ }
+
+ let expanded = quote! {
+ fn get_docs_list() -> Vec<&'static str> {
+ vec![
+ #(#doc_list),*
+ ]
+ }
+ };
+
+ expanded.into()
+}
+
+#[proc_macro]
+pub fn generate_helpdoc_test(_input: TokenStream) -> TokenStream {
+ let manifest_dir =
+ std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get CARGO_MANIFEST_DIR");
+ let repo_root = Path::new(&manifest_dir);
+ let helpdoc_dir = repo_root.join("resources").join("helpdoc");
+
+ if !helpdoc_dir.exists() {
+ return quote! {
+ #[cfg(test)]
+ mod helpdoc_tests {
+ #[test]
+ fn test_no_docs() {
+ }
+ }
+ }
+ .into();
+ }
+
+ let mut doc_entries = Vec::new();
+ scan_directory(&helpdoc_dir, &mut doc_entries, &helpdoc_dir);
+
+ let mut test_cases = Vec::new();
+
+ for (doc_name, lang) in &doc_entries {
+ let test_name_str = format!(
+ "test_doc_{}_{}",
+ doc_name
+ .replace('/', "_")
+ .replace('.', "_")
+ .replace('-', "_"),
+ lang.replace('-', "_")
+ );
+ let test_name = syn::Ident::new(&test_name_str, proc_macro2::Span::call_site());
+ let test_case = quote! {
+ #[test]
+ fn #test_name() {
+ let doc = super::get_doc(#doc_name, #lang);
+ assert!(!doc.is_empty(), "Document {}.{} should not be empty", #doc_name, #lang);
+ }
+ };
+
+ test_cases.push(test_case);
+ }
+
+ let expanded = quote! {
+ #[cfg(test)]
+ mod helpdoc_tests {
+ #(#test_cases)*
+ }
+ };
+
+ expanded.into()
+}