zarz 0.3.1-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ use anyhow::{Context, Result};
2
+ use std::process::Stdio;
3
+ use tokio::io::{AsyncBufReadExt, BufReader};
4
+ use tokio::process::Command;
5
+
6
+ pub struct CommandExecutor;
7
+
8
+ #[derive(Debug)]
9
+ pub struct CommandResult {
10
+ pub stdout: String,
11
+ pub stderr: String,
12
+ pub exit_code: i32,
13
+ pub success: bool,
14
+ }
15
+
16
+ impl CommandExecutor {
17
+ pub async fn execute(command: &str) -> Result<CommandResult> {
18
+ let (shell, flag) = if cfg!(target_os = "windows") {
19
+ ("cmd", "/C")
20
+ } else {
21
+ ("sh", "-c")
22
+ };
23
+
24
+ let mut child = Command::new(shell)
25
+ .arg(flag)
26
+ .arg(command)
27
+ .stdout(Stdio::piped())
28
+ .stderr(Stdio::piped())
29
+ .spawn()
30
+ .with_context(|| format!("Failed to execute command: {}", command))?;
31
+
32
+ let stdout = child
33
+ .stdout
34
+ .take()
35
+ .context("Failed to capture stdout")?;
36
+
37
+ let stderr = child
38
+ .stderr
39
+ .take()
40
+ .context("Failed to capture stderr")?;
41
+
42
+ let mut stdout_lines = BufReader::new(stdout).lines();
43
+ let mut stderr_lines = BufReader::new(stderr).lines();
44
+
45
+ let stdout_handle = tokio::spawn(async move {
46
+ let mut output = String::new();
47
+ while let Ok(Some(line)) = stdout_lines.next_line().await {
48
+ output.push_str(&line);
49
+ output.push('\n');
50
+ }
51
+ output
52
+ });
53
+
54
+ let stderr_handle = tokio::spawn(async move {
55
+ let mut output = String::new();
56
+ while let Ok(Some(line)) = stderr_lines.next_line().await {
57
+ output.push_str(&line);
58
+ output.push('\n');
59
+ }
60
+ output
61
+ });
62
+
63
+ let stdout_output = stdout_handle
64
+ .await
65
+ .context("Failed to join stdout task")?;
66
+
67
+ let stderr_output = stderr_handle
68
+ .await
69
+ .context("Failed to join stderr task")?;
70
+
71
+ let status = child
72
+ .wait()
73
+ .await
74
+ .context("Failed to wait for command")?;
75
+
76
+ let exit_code = status.code().unwrap_or(-1);
77
+ let success = status.success();
78
+
79
+ Ok(CommandResult {
80
+ stdout: stdout_output,
81
+ stderr: stderr_output,
82
+ exit_code,
83
+ success,
84
+ })
85
+ }
86
+
87
+ #[allow(dead_code)]
88
+ pub async fn execute_streaming<F>(command: &str, mut on_output: F) -> Result<CommandResult>
89
+ where
90
+ F: FnMut(&str) + Send,
91
+ {
92
+ let (shell, flag) = if cfg!(target_os = "windows") {
93
+ ("cmd", "/C")
94
+ } else {
95
+ ("sh", "-c")
96
+ };
97
+
98
+ let mut child = Command::new(shell)
99
+ .arg(flag)
100
+ .arg(command)
101
+ .stdout(Stdio::piped())
102
+ .stderr(Stdio::piped())
103
+ .spawn()
104
+ .with_context(|| format!("Failed to execute command: {}", command))?;
105
+
106
+ let stdout = child
107
+ .stdout
108
+ .take()
109
+ .context("Failed to capture stdout")?;
110
+
111
+ let stderr = child
112
+ .stderr
113
+ .take()
114
+ .context("Failed to capture stderr")?;
115
+
116
+ let mut stdout_lines = BufReader::new(stdout).lines();
117
+ let mut stderr_lines = BufReader::new(stderr).lines();
118
+
119
+ let mut stdout_output = String::new();
120
+ let mut stderr_output = String::new();
121
+
122
+ loop {
123
+ tokio::select! {
124
+ result = stdout_lines.next_line() => {
125
+ match result {
126
+ Ok(Some(line)) => {
127
+ on_output(&line);
128
+ stdout_output.push_str(&line);
129
+ stdout_output.push('\n');
130
+ }
131
+ Ok(None) => break,
132
+ Err(_) => break,
133
+ }
134
+ }
135
+ result = stderr_lines.next_line() => {
136
+ match result {
137
+ Ok(Some(line)) => {
138
+ on_output(&line);
139
+ stderr_output.push_str(&line);
140
+ stderr_output.push('\n');
141
+ }
142
+ Ok(None) => {},
143
+ Err(_) => {},
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ let status = child
150
+ .wait()
151
+ .await
152
+ .context("Failed to wait for command")?;
153
+
154
+ let exit_code = status.code().unwrap_or(-1);
155
+ let success = status.success();
156
+
157
+ Ok(CommandResult {
158
+ stdout: stdout_output,
159
+ stderr: stderr_output,
160
+ exit_code,
161
+ success,
162
+ })
163
+ }
164
+ }
package/src/fs_ops.rs ADDED
@@ -0,0 +1,117 @@
1
+ use anyhow::{Context, Result};
2
+ use std::path::{Path, PathBuf};
3
+ use tokio::fs;
4
+ use walkdir::WalkDir;
5
+
6
+ pub struct FileSystemOps;
7
+
8
+ impl FileSystemOps {
9
+ pub async fn create_file(path: &Path, content: &str) -> Result<()> {
10
+ if let Some(parent) = path.parent() {
11
+ fs::create_dir_all(parent)
12
+ .await
13
+ .with_context(|| format!("Failed to create parent directories for {}", path.display()))?;
14
+ }
15
+
16
+ fs::write(path, content)
17
+ .await
18
+ .with_context(|| format!("Failed to write file {}", path.display()))?;
19
+
20
+ Ok(())
21
+ }
22
+
23
+ #[allow(dead_code)]
24
+ pub async fn delete_file(path: &Path) -> Result<()> {
25
+ fs::remove_file(path)
26
+ .await
27
+ .with_context(|| format!("Failed to delete file {}", path.display()))?;
28
+
29
+ Ok(())
30
+ }
31
+
32
+ #[allow(dead_code)]
33
+ pub async fn rename_file(from: &Path, to: &Path) -> Result<()> {
34
+ if let Some(parent) = to.parent() {
35
+ fs::create_dir_all(parent)
36
+ .await
37
+ .with_context(|| format!("Failed to create parent directories for {}", to.display()))?;
38
+ }
39
+
40
+ fs::rename(from, to)
41
+ .await
42
+ .with_context(|| format!("Failed to rename {} to {}", from.display(), to.display()))?;
43
+
44
+ Ok(())
45
+ }
46
+
47
+ #[allow(dead_code)]
48
+ pub async fn create_directory(path: &Path) -> Result<()> {
49
+ fs::create_dir_all(path)
50
+ .await
51
+ .with_context(|| format!("Failed to create directory {}", path.display()))?;
52
+
53
+ Ok(())
54
+ }
55
+
56
+ pub async fn read_file(path: &Path) -> Result<String> {
57
+ fs::read_to_string(path)
58
+ .await
59
+ .with_context(|| format!("Failed to read file {}", path.display()))
60
+ }
61
+
62
+ pub async fn file_exists(path: &Path) -> bool {
63
+ fs::metadata(path).await.is_ok()
64
+ }
65
+
66
+ #[allow(dead_code)]
67
+ pub fn list_files(root: &Path, pattern: Option<&str>) -> Result<Vec<PathBuf>> {
68
+ let mut files = Vec::new();
69
+
70
+ for entry in WalkDir::new(root)
71
+ .follow_links(false)
72
+ .into_iter()
73
+ .filter_map(|e| e.ok())
74
+ {
75
+ if entry.file_type().is_file() {
76
+ let path = entry.path();
77
+
78
+ if let Some(pattern) = pattern {
79
+ if let Some(file_name) = path.file_name() {
80
+ if file_name.to_string_lossy().contains(pattern) {
81
+ files.push(path.to_path_buf());
82
+ }
83
+ }
84
+ } else {
85
+ files.push(path.to_path_buf());
86
+ }
87
+ }
88
+ }
89
+
90
+ Ok(files)
91
+ }
92
+
93
+ #[allow(dead_code)]
94
+ pub fn get_directory_structure(root: &Path, max_depth: Option<usize>) -> Result<String> {
95
+ let mut output = String::new();
96
+ let max_depth = max_depth.unwrap_or(3);
97
+
98
+ for entry in WalkDir::new(root)
99
+ .max_depth(max_depth)
100
+ .follow_links(false)
101
+ .into_iter()
102
+ .filter_map(|e| e.ok())
103
+ {
104
+ let depth = entry.depth();
105
+ let indent = " ".repeat(depth);
106
+ let name = entry.file_name().to_string_lossy();
107
+
108
+ if entry.file_type().is_dir() {
109
+ output.push_str(&format!("{}{}/\n", indent, name));
110
+ } else {
111
+ output.push_str(&format!("{}{}\n", indent, name));
112
+ }
113
+ }
114
+
115
+ Ok(output)
116
+ }
117
+ }
@@ -0,0 +1,143 @@
1
+ use anyhow::Result;
2
+ use regex::Regex;
3
+ use std::collections::HashSet;
4
+ use std::path::{Path, PathBuf};
5
+ use walkdir::WalkDir;
6
+
7
+ pub struct ContextBuilder;
8
+
9
+ impl ContextBuilder {
10
+ pub fn build_context(root: &Path, query: &str) -> Result<Vec<PathBuf>> {
11
+ let keywords = Self::extract_keywords(query);
12
+ let mut relevant_files = Vec::new();
13
+ let mut scores: Vec<(PathBuf, usize)> = Vec::new();
14
+
15
+ for entry in WalkDir::new(root)
16
+ .max_depth(10)
17
+ .follow_links(false)
18
+ .into_iter()
19
+ .filter_map(|e| e.ok())
20
+ {
21
+ if entry.file_type().is_file() {
22
+ let path = entry.path();
23
+
24
+ if Self::should_skip(path) {
25
+ continue;
26
+ }
27
+
28
+ if let Ok(content) = std::fs::read_to_string(path) {
29
+ let score = Self::calculate_relevance(&content, &keywords);
30
+
31
+ if score > 0 {
32
+ scores.push((path.to_path_buf(), score));
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ scores.sort_by(|a, b| b.1.cmp(&a.1));
39
+
40
+ for (path, _) in scores.iter().take(5) {
41
+ relevant_files.push(path.clone());
42
+ }
43
+
44
+ Ok(relevant_files)
45
+ }
46
+
47
+ fn extract_keywords(query: &str) -> HashSet<String> {
48
+ let re = Regex::new(r"\b[a-zA-Z_][a-zA-Z0-9_]{2,}\b").unwrap();
49
+ let mut keywords = HashSet::new();
50
+
51
+ for cap in re.find_iter(query) {
52
+ let word = cap.as_str().to_lowercase();
53
+ if !Self::is_common_word(&word) {
54
+ keywords.insert(word);
55
+ }
56
+ }
57
+
58
+ keywords
59
+ }
60
+
61
+ fn calculate_relevance(content: &str, keywords: &HashSet<String>) -> usize {
62
+ let content_lower = content.to_lowercase();
63
+ let mut score = 0;
64
+
65
+ for keyword in keywords {
66
+ let count = content_lower.matches(keyword.as_str()).count();
67
+ score += count * 10;
68
+ }
69
+
70
+ score
71
+ }
72
+
73
+ fn should_skip(path: &Path) -> bool {
74
+ let path_str = path.to_string_lossy();
75
+
76
+ if path_str.contains("target/")
77
+ || path_str.contains(".git/")
78
+ || path_str.contains("node_modules/")
79
+ || path_str.contains(".vscode/")
80
+ {
81
+ return true;
82
+ }
83
+
84
+ if let Some(ext) = path.extension() {
85
+ let ext_str = ext.to_string_lossy();
86
+ if ext_str == "lock"
87
+ || ext_str == "json"
88
+ || ext_str == "md"
89
+ || ext_str == "txt"
90
+ || ext_str == "yml"
91
+ || ext_str == "yaml"
92
+ {
93
+ return true;
94
+ }
95
+ }
96
+
97
+ false
98
+ }
99
+
100
+ fn is_common_word(word: &str) -> bool {
101
+ matches!(
102
+ word,
103
+ "the" | "and"
104
+ | "for"
105
+ | "are"
106
+ | "but"
107
+ | "not"
108
+ | "you"
109
+ | "all"
110
+ | "can"
111
+ | "her"
112
+ | "was"
113
+ | "one"
114
+ | "our"
115
+ | "out"
116
+ | "day"
117
+ | "get"
118
+ | "has"
119
+ | "him"
120
+ | "his"
121
+ | "how"
122
+ | "let"
123
+ | "may"
124
+ | "new"
125
+ | "now"
126
+ | "old"
127
+ | "see"
128
+ | "try"
129
+ | "use"
130
+ | "way"
131
+ | "who"
132
+ | "boy"
133
+ | "did"
134
+ | "its"
135
+ | "say"
136
+ | "she"
137
+ | "too"
138
+ | "any"
139
+ | "add"
140
+ | "set"
141
+ )
142
+ }
143
+ }
@@ -0,0 +1,60 @@
1
+ mod rust_parser;
2
+ mod symbol_search;
3
+ mod context;
4
+
5
+ pub use rust_parser::RustParser;
6
+ pub use symbol_search::SymbolSearcher;
7
+ pub use context::ContextBuilder;
8
+
9
+ use anyhow::Result;
10
+ use std::path::{Path, PathBuf};
11
+
12
+ #[derive(Debug, Clone)]
13
+ pub struct Symbol {
14
+ pub name: String,
15
+ pub kind: SymbolKind,
16
+ pub file: PathBuf,
17
+ #[allow(dead_code)]
18
+ pub line: usize,
19
+ }
20
+
21
+ #[derive(Debug, Clone, PartialEq)]
22
+ pub enum SymbolKind {
23
+ Function,
24
+ Struct,
25
+ Enum,
26
+ Trait,
27
+ Impl,
28
+ Module,
29
+ Constant,
30
+ Static,
31
+ }
32
+
33
+ #[derive(Debug)]
34
+ pub struct ProjectIntelligence {
35
+ root: PathBuf,
36
+ }
37
+
38
+ impl ProjectIntelligence {
39
+ pub fn new(root: PathBuf) -> Self {
40
+ Self { root }
41
+ }
42
+
43
+ pub fn find_symbol(&self, name: &str) -> Result<Vec<Symbol>> {
44
+ SymbolSearcher::search(&self.root, name)
45
+ }
46
+
47
+ #[allow(dead_code)]
48
+ pub fn get_file_symbols(&self, file: &Path) -> Result<Vec<Symbol>> {
49
+ RustParser::parse_file(file)
50
+ }
51
+
52
+ pub fn get_relevant_context(&self, query: &str) -> Result<Vec<PathBuf>> {
53
+ ContextBuilder::build_context(&self.root, query)
54
+ }
55
+
56
+ #[allow(dead_code)]
57
+ pub fn analyze_dependencies(&self) -> Result<Vec<String>> {
58
+ RustParser::extract_dependencies(&self.root)
59
+ }
60
+ }
@@ -0,0 +1,141 @@
1
+ use super::{Symbol, SymbolKind};
2
+ use anyhow::{Context, Result};
3
+ use std::path::{Path, PathBuf};
4
+ use syn::{visit::Visit, Item};
5
+
6
+ pub struct RustParser;
7
+
8
+ impl RustParser {
9
+ pub fn parse_file(path: &Path) -> Result<Vec<Symbol>> {
10
+ let content = std::fs::read_to_string(path)
11
+ .with_context(|| format!("Failed to read file {}", path.display()))?;
12
+
13
+ let syntax = syn::parse_file(&content)
14
+ .with_context(|| format!("Failed to parse Rust file {}", path.display()))?;
15
+
16
+ let mut visitor = SymbolVisitor {
17
+ symbols: Vec::new(),
18
+ file: path.to_path_buf(),
19
+ };
20
+
21
+ visitor.visit_file(&syntax);
22
+
23
+ Ok(visitor.symbols)
24
+ }
25
+
26
+ #[allow(dead_code)]
27
+ pub fn extract_dependencies(root: &Path) -> Result<Vec<String>> {
28
+ let cargo_path = root.join("Cargo.toml");
29
+
30
+ if !cargo_path.exists() {
31
+ return Ok(Vec::new());
32
+ }
33
+
34
+ let content = std::fs::read_to_string(&cargo_path)
35
+ .context("Failed to read Cargo.toml")?;
36
+
37
+ let toml: toml::Value = toml::from_str(&content)
38
+ .context("Failed to parse Cargo.toml")?;
39
+
40
+ let mut deps = Vec::new();
41
+
42
+ if let Some(dependencies) = toml.get("dependencies") {
43
+ if let Some(table) = dependencies.as_table() {
44
+ for (name, _) in table {
45
+ deps.push(name.clone());
46
+ }
47
+ }
48
+ }
49
+
50
+ Ok(deps)
51
+ }
52
+ }
53
+
54
+ struct SymbolVisitor {
55
+ symbols: Vec<Symbol>,
56
+ file: PathBuf,
57
+ }
58
+
59
+ impl<'ast> Visit<'ast> for SymbolVisitor {
60
+ fn visit_item(&mut self, item: &'ast Item) {
61
+ match item {
62
+ Item::Fn(func) => {
63
+ let name = func.sig.ident.to_string();
64
+ self.symbols.push(Symbol {
65
+ name,
66
+ kind: SymbolKind::Function,
67
+ file: self.file.clone(),
68
+ line: 0,
69
+ });
70
+ }
71
+ Item::Struct(s) => {
72
+ let name = s.ident.to_string();
73
+ self.symbols.push(Symbol {
74
+ name,
75
+ kind: SymbolKind::Struct,
76
+ file: self.file.clone(),
77
+ line: 0,
78
+ });
79
+ }
80
+ Item::Enum(e) => {
81
+ let name = e.ident.to_string();
82
+ self.symbols.push(Symbol {
83
+ name,
84
+ kind: SymbolKind::Enum,
85
+ file: self.file.clone(),
86
+ line: 0,
87
+ });
88
+ }
89
+ Item::Trait(t) => {
90
+ let name = t.ident.to_string();
91
+ self.symbols.push(Symbol {
92
+ name,
93
+ kind: SymbolKind::Trait,
94
+ file: self.file.clone(),
95
+ line: 0,
96
+ });
97
+ }
98
+ Item::Impl(impl_item) => {
99
+ if let Some((_, path, _)) = &impl_item.trait_ {
100
+ let name = quote::quote!(#path).to_string();
101
+ self.symbols.push(Symbol {
102
+ name,
103
+ kind: SymbolKind::Impl,
104
+ file: self.file.clone(),
105
+ line: 0,
106
+ });
107
+ }
108
+ }
109
+ Item::Mod(m) => {
110
+ let name = m.ident.to_string();
111
+ self.symbols.push(Symbol {
112
+ name,
113
+ kind: SymbolKind::Module,
114
+ file: self.file.clone(),
115
+ line: 0,
116
+ });
117
+ }
118
+ Item::Const(c) => {
119
+ let name = c.ident.to_string();
120
+ self.symbols.push(Symbol {
121
+ name,
122
+ kind: SymbolKind::Constant,
123
+ file: self.file.clone(),
124
+ line: 0,
125
+ });
126
+ }
127
+ Item::Static(s) => {
128
+ let name = s.ident.to_string();
129
+ self.symbols.push(Symbol {
130
+ name,
131
+ kind: SymbolKind::Static,
132
+ file: self.file.clone(),
133
+ line: 0,
134
+ });
135
+ }
136
+ _ => {}
137
+ }
138
+
139
+ syn::visit::visit_item(self, item);
140
+ }
141
+ }