ricecoder_research/dependency_analyzer/
ruby_parser.rs1use crate::error::ResearchError;
4use crate::models::Dependency;
5use std::path::Path;
6use tracing::debug;
7
8#[derive(Debug)]
10pub struct RubyParser;
11
12impl RubyParser {
13 pub fn new() -> Self {
15 RubyParser
16 }
17
18 pub fn parse(&self, root: &Path) -> Result<Vec<Dependency>, ResearchError> {
20 let gemfile_path = root.join("Gemfile");
21
22 if !gemfile_path.exists() {
23 return Ok(Vec::new());
24 }
25
26 debug!("Parsing Ruby dependencies from {:?}", gemfile_path);
27
28 let content = std::fs::read_to_string(&gemfile_path).map_err(|e| {
29 ResearchError::DependencyParsingFailed {
30 language: "Ruby".to_string(),
31 path: Some(gemfile_path.clone()),
32 reason: format!("Failed to read Gemfile: {}", e),
33 }
34 })?;
35
36 let mut dependencies = Vec::new();
37
38 let gem_pattern =
43 regex::Regex::new(r#"gem\s+['"]([a-zA-Z0-9_\-\.]+)['"](?:\s*,\s*['"]([^'"]+)['"])?"#)
44 .unwrap();
45
46 for cap in gem_pattern.captures_iter(&content) {
47 let name = cap.get(1).map(|m| m.as_str()).unwrap_or("");
48 let version = cap.get(2).map(|m| m.as_str()).unwrap_or("*");
49
50 dependencies.push(Dependency {
51 name: name.to_string(),
52 version: version.to_string(),
53 constraints: Some(version.to_string()),
54 is_dev: false,
55 });
56 }
57
58 Ok(dependencies)
59 }
60
61 pub fn has_manifest(&self, root: &Path) -> bool {
63 root.join("Gemfile").exists()
64 }
65}
66
67impl Default for RubyParser {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use std::fs;
77 use tempfile::TempDir;
78
79 #[test]
80 fn test_ruby_parser_creation() {
81 let parser = RubyParser::new();
82 assert!(true);
83 }
84
85 #[test]
86 fn test_ruby_parser_no_manifest() {
87 let parser = RubyParser::new();
88 let temp_dir = TempDir::new().unwrap();
89 let result = parser.parse(temp_dir.path()).unwrap();
90 assert!(result.is_empty());
91 }
92
93 #[test]
94 fn test_ruby_parser_simple_dependencies() {
95 let parser = RubyParser::new();
96 let temp_dir = TempDir::new().unwrap();
97
98 let gemfile = r#"
99source 'https://rubygems.org'
100
101gem 'rails', '~> 7.0'
102gem 'pg', '~> 1.1'
103gem 'puma', '~> 5.0'
104
105group :development, :test do
106 gem 'rspec-rails', '~> 5.0'
107end
108"#;
109
110 fs::write(temp_dir.path().join("Gemfile"), gemfile).unwrap();
111
112 let deps = parser.parse(temp_dir.path()).unwrap();
113 assert!(deps.len() >= 3);
114
115 let rails = deps.iter().find(|d| d.name == "rails").unwrap();
116 assert_eq!(rails.version, "~> 7.0");
117 }
118
119 #[test]
120 fn test_ruby_parser_has_manifest() {
121 let parser = RubyParser::new();
122 let temp_dir = TempDir::new().unwrap();
123
124 assert!(!parser.has_manifest(temp_dir.path()));
125
126 fs::write(temp_dir.path().join("Gemfile"), "").unwrap();
127 assert!(parser.has_manifest(temp_dir.path()));
128 }
129}