diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 20c3da3..1842f18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "1.0.0" @@ -64,6 +73,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "clap" version = "4.6.0" @@ -131,6 +163,18 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "drop_bomb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -201,6 +245,26 @@ dependencies = [ "slab", ] +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.5.0" @@ -222,12 +286,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -240,6 +325,12 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.8.0" @@ -276,6 +367,44 @@ dependencies = [ "stfu8", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -288,6 +417,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -306,6 +444,111 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" + +[[package]] +name = "ruff_python_ast" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff?rev=v0.4.0#e751b4ea8260ff83723345d1c7d39d5c776cc8ff" +dependencies = [ + "aho-corasick", + "bitflags", + "is-macro", + "itertools", + "once_cell", + "ruff_python_trivia", + "ruff_source_file", + "ruff_text_size", + "rustc-hash", +] + +[[package]] +name = "ruff_python_parser" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff?rev=v0.4.0#e751b4ea8260ff83723345d1c7d39d5c776cc8ff" +dependencies = [ + "anyhow", + "bitflags", + "bstr", + "drop_bomb", + "is-macro", + "itertools", + "memchr", + "ruff_python_ast", + "ruff_text_size", + "rustc-hash", + "static_assertions", + "unicode-ident", + "unicode-normalization", + "unicode_names2", +] + +[[package]] +name = "ruff_python_trivia" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff?rev=v0.4.0#e751b4ea8260ff83723345d1c7d39d5c776cc8ff" +dependencies = [ + "itertools", + "ruff_source_file", + "ruff_text_size", + "unicode-ident", +] + +[[package]] +name = "ruff_source_file" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff?rev=v0.4.0#e751b4ea8260ff83723345d1c7d39d5c776cc8ff" +dependencies = [ + "memchr", + "once_cell", + "ruff_text_size", +] + +[[package]] +name = "ruff_text_size" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff?rev=v0.4.0#e751b4ea8260ff83723345d1c7d39d5c776cc8ff" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "same-file" version = "1.0.6" @@ -358,12 +601,24 @@ dependencies = [ "zmij", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_prelude" version = "0.2.12" @@ -472,12 +727,43 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode_names2" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd" +dependencies = [ + "phf", + "unicode_names2_generator", +] + +[[package]] +name = "unicode_names2_generator" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e" +dependencies = [ + "getopts", + "log", + "phf_codegen", + "rand", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -493,6 +779,8 @@ dependencies = [ "console", "indicator", "path_abs", + "ruff_python_ast", + "ruff_python_parser", "serde", "serde_json", "walkdir", @@ -508,6 +796,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "winapi-util" version = "0.1.11" @@ -532,6 +826,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index d8ec0c5..3a47d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" path_abs = "0.5" console = "0.16.3" -indicator = "0.4.4" \ No newline at end of file +indicator = "0.4.4" +ruff_python_parser = { git = "https://github.com/astral-sh/ruff", rev = "v0.4.0" } +ruff_python_ast = { git = "https://github.com/astral-sh/ruff", rev = "v0.4.0" } \ No newline at end of file diff --git a/src/installer.rs b/src/installer.rs index e69de29..1d5ece4 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -0,0 +1,2 @@ +pub mod git; +pub mod install; \ No newline at end of file diff --git a/src/installer/git.rs b/src/installer/git.rs new file mode 100644 index 0000000..51a35d3 --- /dev/null +++ b/src/installer/git.rs @@ -0,0 +1,57 @@ +use crate::installer::install::Installer; +use std::process::Command; +use anyhow::{Context, Result, anyhow}; +use std::fs; +use std::path::Path; +use std::path::PathBuf; + +pub struct GitInstaller{ + git_repo_url: String, + cache_path: Option, +} + +impl GitInstaller { + // 1. 增加一个关联函数 new,负责“一键初始化” + pub fn new(git_repo_url: &str, root_cache_path: &str) -> Self { + // 逻辑:从 URL 提取仓库名(总督的直觉) + let repo_name = git_repo_url + .split('/') + .last() + .unwrap_or("default_repo") + .trim_end_matches(".git"); + let mut path_buf = PathBuf::from(root_cache_path); + path_buf.push(repo_name); + + // 返回实例 + Self { + git_repo_url: git_repo_url.to_owned(), + cache_path: Some(path_buf.display().to_string()), + } + } +} +impl Installer for GitInstaller{ + fn download(&self) -> Result<()> { + let cache_path = self.cache_path.as_ref() + .context("错误:未初始化缓存路径,请先调用 get_cache_path")?; + if Path::new(cache_path).exists() { + fs::remove_dir_all(cache_path) + .with_context(|| format!("无法清理旧的缓存目录: {}", cache_path))?; + }; + println!("viceroy正在安装"); + let output = Command::new("git") + .args(["clone", + "--depth", + "1", + &self.git_repo_url, + cache_path]) + .output() + .context("执行 Git 失败,请确认系统已安装 git 并配置了 SSH/HTTP 权限")?; + if output.status.success() { + println!("✅ 技能包拉取成功。"); + Ok(()) + } else { + let err_msg = String::from_utf8_lossy(&output.stderr); + Err(anyhow!("Git 克隆失败: {}", err_msg)) + } + } +} diff --git a/src/installer/install.rs b/src/installer/install.rs new file mode 100644 index 0000000..f6b6612 --- /dev/null +++ b/src/installer/install.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub trait Installer { + fn download(&self) -> Result<()>; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 51f93ae..354fac9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ -mod installer; -mod manifest; \ No newline at end of file +pub mod installer; +pub mod manifest; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..4b02c1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ + + fn main() { - println!("Hello, world!"); + + } diff --git a/src/manifest.rs b/src/manifest.rs index e69de29..5d6c58e 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -0,0 +1 @@ +mod skill; diff --git a/src/manifest/skill.rs b/src/manifest/skill.rs new file mode 100644 index 0000000..b5d30c8 --- /dev/null +++ b/src/manifest/skill.rs @@ -0,0 +1,4 @@ +pub mod skill; +pub mod model; +pub mod analysis; +pub mod skill_structure_tree; \ No newline at end of file diff --git a/src/manifest/skill/analysis.rs b/src/manifest/skill/analysis.rs new file mode 100644 index 0000000..c51b6bb --- /dev/null +++ b/src/manifest/skill/analysis.rs @@ -0,0 +1,53 @@ +use crate::manifest::skill::skill_structure_tree::{SkillNode,PythonFuncNode,PythonFileNode}; +use walkdir::WalkDir; +use std::path::Path; +use std::collections::HashMap; +use ruff_python_parser::{parse, Mode}; +use ruff_python_ast::{Mod, Stmt}; + +fn analyze_python_file(code: &str) -> PythonFileNode { + let mut func_dict = HashMap::new(); + let parsed: Mod = parse(code, Mode::Module).expect("Python 语法错误"); + if let Mod::Module(module) = parsed { + for stmt in module.body { + if let Stmt::FunctionDef(func) = stmt { + let func_name = func.name.to_string(); + let args = HashMap::new(); + let doc = "从 AST 里抠出来的文档".to_string(); + func_dict.insert(func_name.clone(), PythonFuncNode { + func_sign: format!("def {}(...)", func_name), + func_args: args, + func_return_type: "Unknown".into(), + func_docs: doc, + }); + } + } + } + PythonFileNode { + file_name: "xxx".into(), + func_dict, + } +} + +fn main_scan_logic(root_path: &Path) { + let mut root_node = SkillNode::new_folder("root"); + for entry in WalkDir::new(root_path).into_iter().filter_map(|e| e.ok()) { + let path = entry.path(); + if path.is_file() { + let rel_path = path.strip_prefix(root_path).unwrap(); + let segments: Vec<&str> = rel_path.iter().map(|s| s.to_str().unwrap()).collect(); + let node = if path.extension().and_then(|s| s.to_str()) == Some("py") { + let code = std::fs::read_to_string(path).expect("读取文件失败"); + let mut py_node = analyze_python_file(&code); + py_node.file_name = path.file_stem().unwrap().to_string_lossy().into(); + SkillNode::Python(py_node) + } else { + let file_name = path.file_name().unwrap().to_string_lossy().into(); + SkillNode::File(file_name) + }; + root_node.insert_recursive(&segments, node); + } + } + let json = serde_json::to_string_pretty(&root_node).unwrap(); + println!("{}", json); +} diff --git a/src/manifest/skill/model.rs b/src/manifest/skill/model.rs new file mode 100644 index 0000000..bb8684e --- /dev/null +++ b/src/manifest/skill/model.rs @@ -0,0 +1,7 @@ +pub struct SkillModel{ + pub skill_path: String, +} + +pub struct SkillRegister{ + +} \ No newline at end of file diff --git a/src/manifest/skill/skill.rs b/src/manifest/skill/skill.rs new file mode 100644 index 0000000..de706b1 --- /dev/null +++ b/src/manifest/skill/skill.rs @@ -0,0 +1,28 @@ +use crate::installer::git; +use crate::installer::install::Installer; +use crate::manifest::skill::model::SkillModel; +use std::path::PathBuf; + +impl SkillModel{ + fn install(git_repo_url: String, root_cache_path: String, relative_path: String) -> Self{ + let git_installer = git::GitInstaller::new(&git_repo_url, &root_cache_path); + if let Err(e) = git_installer.download() { + eprintln!("安装失败: {}", e); + }; + let git_repo_name = git_repo_url + .split('/') + .last() + .unwrap_or("default_repo") + .trim_end_matches(".git"); + let mut path_builder = PathBuf::from(&root_cache_path); + path_builder.push(git_repo_name); + path_builder.push(&relative_path); + let skill_path = path_builder.to_string_lossy().to_string(); + Self{ + skill_path, + } + } + fn analysis(&self){ + } +} + diff --git a/src/manifest/skill/skill_structure_tree.rs b/src/manifest/skill/skill_structure_tree.rs new file mode 100644 index 0000000..3c3f070 --- /dev/null +++ b/src/manifest/skill/skill_structure_tree.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; +use serde::Serialize; + +#[derive(Serialize)] +pub struct PythonFuncNode{ + pub func_sign: String, + pub func_args: HashMap, + pub func_return_type: String, + pub func_docs: String, +} + +#[derive(Serialize)] +pub struct PythonFileNode{ + pub file_name: String, + pub func_dict: HashMap, +} + +#[derive(Serialize)] +#[serde(tag = "type", content = "data")] +pub enum SkillNode { + Folder(String, HashMap), + Python(PythonFileNode), + File(String), +} + +impl SkillNode { + pub fn new_folder(name: &str) -> Self { + SkillNode::Folder(name.to_string(), HashMap::new()) + } + + pub fn insert_recursive(&mut self, segments: &[&str], data: SkillNode) { + if let SkillNode::Folder(_name, children) = self { + let current_seg = segments[0]; + if segments.len() == 1 { + children.insert(current_seg.to_string(), data); + } else { + let next_node = children + .entry(current_seg.to_string()) + .or_insert_with(|| SkillNode::new_folder(current_seg)); + + next_node.insert_recursive(&segments[1..], data); + } + } else { + panic!("试图在已存在文件{}内保存文件", segments[0]); + } + } +} \ No newline at end of file