use anyhow::{Context, Result};
use codebank::{Bank, BankConfig, BankStrategy, CodeBank};
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tokenizers::tokenizer::Tokenizer;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Dependency {
pub name: String,
pub version: String,
}
impl Dependency {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
}
}
pub fn get_registry_path(&self, registry_base_path: &Path) -> PathBuf {
registry_base_path.join(format!("{}-{}", self.name, self.version))
}
pub fn is_available_in_registry(&self, registry_base_path: &Path) -> bool {
let path = self.get_registry_path(registry_base_path);
path.exists() && path.is_dir()
}
}
#[derive(Debug, Default)]
pub struct DependencyCollection {
deps: Vec<Dependency>,
}
impl DependencyCollection {
pub fn new() -> Self {
Self { deps: Vec::new() }
}
pub fn add(&mut self, dep: Dependency) {
self.deps.push(dep);
}
pub fn len(&self) -> usize {
self.deps.len()
}
pub fn is_empty(&self) -> bool {
self.deps.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Dependency> {
self.deps.iter()
}
pub fn get(&self, name: &str) -> Option<&Dependency> {
self.deps.iter().find(|dep| dep.name == name)
}
pub fn get_version(&self, name: &str) -> Option<&String> {
self.get(name).map(|dep| &dep.version)
}
pub fn to_map(&self) -> HashMap<String, String> {
self.deps
.iter()
.map(|dep| (dep.name.clone(), dep.version.clone()))
.collect()
}
pub fn from_map(map: &HashMap<String, String>) -> Self {
let mut collection = Self::new();
for (name, version) in map {
collection.add(Dependency::new(name, version));
}
collection
}
pub fn contains_name(&self, name: &str) -> bool {
self.deps.iter().any(|dep| dep.name == name)
}
pub fn contains(&self, name: &str, version: &str) -> bool {
self.deps
.iter()
.any(|dep| dep.name == name && dep.version == version)
}
pub fn filter_available(&self, registry_path: &Path) -> Self {
let mut result = Self::new();
for dep in &self.deps {
if dep.is_available_in_registry(registry_path) {
result.add(dep.clone());
}
}
result
}
pub fn as_slice(&self) -> &[Dependency] {
&self.deps
}
}
pub fn find_cargo_toml_files<P: AsRef<Path>>(root_dir: P) -> Result<Vec<PathBuf>> {
let root_dir = root_dir.as_ref();
let mut cargo_toml_files = Vec::new();
if !root_dir.exists() {
return Err(anyhow::anyhow!(
"Root directory does not exist: {}",
root_dir.display()
));
}
if !root_dir.is_dir() {
return Err(anyhow::anyhow!(
"Path is not a directory: {}",
root_dir.display()
));
}
find_cargo_toml_files_recursive(root_dir, &mut cargo_toml_files)?;
Ok(cargo_toml_files)
}
fn find_cargo_toml_files_recursive(dir: &Path, cargo_toml_files: &mut Vec<PathBuf>) -> Result<()> {
for entry in
fs::read_dir(dir).with_context(|| format!("Failed to read directory: {}", dir.display()))?
{
let entry = entry
.with_context(|| format!("Failed to read directory entry in {}", dir.display()))?;
let path = entry.path();
if path
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.starts_with('.'))
.unwrap_or(false)
&& path.is_dir()
{
continue;
}
if path.file_name().is_some_and(|name| name == "Cargo.toml") {
cargo_toml_files.push(path.clone());
}
if path.is_dir() {
find_cargo_toml_files_recursive(&path, cargo_toml_files)?;
}
}
Ok(())
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum CargoDepSpec {
Simple(String),
Detailed(HashMap<String, toml::Value>),
}
#[derive(Debug, Deserialize)]
struct CargoToml {
#[serde(default)]
dependencies: HashMap<String, CargoDepSpec>,
#[serde(default)]
#[serde(rename = "dev-dependencies")]
dev_dependencies: HashMap<String, CargoDepSpec>,
#[serde(default)]
#[serde(rename = "build-dependencies")]
build_dependencies: HashMap<String, CargoDepSpec>,
}
pub fn collect_dependencies(cargo_toml_files: &[PathBuf]) -> Result<HashSet<String>> {
let mut dependencies = HashSet::new();
for path in cargo_toml_files {
let cargo_toml_content = fs::read_to_string(path)
.with_context(|| format!("Failed to read Cargo.toml file: {}", path.display()))?;
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content)
.with_context(|| format!("Failed to parse Cargo.toml file: {}", path.display()))?;
for dep_name in cargo_toml.dependencies.keys() {
dependencies.insert(dep_name.clone());
}
for dep_name in cargo_toml.dev_dependencies.keys() {
dependencies.insert(dep_name.clone());
}
for dep_name in cargo_toml.build_dependencies.keys() {
dependencies.insert(dep_name.clone());
}
}
Ok(dependencies)
}
pub fn extract_dependency_info(cargo_toml_path: &Path) -> Result<DependencyCollection> {
let mut dependencies = DependencyCollection::new();
let cargo_toml_content = fs::read_to_string(cargo_toml_path).with_context(|| {
format!(
"Failed to read Cargo.toml file: {}",
cargo_toml_path.display()
)
})?;
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content).with_context(|| {
format!(
"Failed to parse Cargo.toml file: {}",
cargo_toml_path.display()
)
})?;
for (name, spec) in &cargo_toml.dependencies {
let version = extract_version_from_spec(spec);
dependencies.add(Dependency::new(name, version));
}
for (name, spec) in &cargo_toml.dev_dependencies {
let version = extract_version_from_spec(spec);
dependencies.add(Dependency::new(name, version));
}
for (name, spec) in &cargo_toml.build_dependencies {
let version = extract_version_from_spec(spec);
dependencies.add(Dependency::new(name, version));
}
Ok(dependencies)
}
fn extract_version_from_spec(spec: &CargoDepSpec) -> String {
match spec {
CargoDepSpec::Simple(version) => version.clone(),
CargoDepSpec::Detailed(table) => {
if let Some(version) = table.get("version") {
if let Some(v) = version.as_str() {
return v.to_string();
}
}
"*".to_string() }
}
}
#[derive(Debug, Deserialize)]
struct CargoLockPackage {
name: String,
version: String,
#[allow(dead_code)] source: Option<String>,
}
#[derive(Debug, Deserialize)]
struct CargoLock {
#[serde(default)]
package: Vec<CargoLockPackage>,
}
pub fn resolve_dependency_versions<P: AsRef<Path>>(
cargo_lock_path: P,
dependencies: &DependencyCollection,
) -> Result<DependencyCollection> {
let cargo_lock_path = cargo_lock_path.as_ref();
if !cargo_lock_path.exists() {
return Err(anyhow::anyhow!(
"Cargo.lock file does not exist: {}",
cargo_lock_path.display()
));
}
let cargo_lock_content = fs::read_to_string(cargo_lock_path).with_context(|| {
format!(
"Failed to read Cargo.lock file: {}",
cargo_lock_path.display()
)
})?;
let cargo_lock: CargoLock = toml::from_str(&cargo_lock_content).with_context(|| {
format!(
"Failed to parse Cargo.lock file: {}",
cargo_lock_path.display()
)
})?;
let mut resolved_versions = DependencyCollection::new();
let mut package_versions: HashMap<String, Vec<String>> = HashMap::new();
for package in &cargo_lock.package {
package_versions
.entry(package.name.clone())
.or_default()
.push(package.version.clone());
}
for dep in dependencies.as_slice() {
if let Some(versions) = package_versions.get(&dep.name) {
if let Some(version) = versions.last() {
resolved_versions.add(Dependency::new(&dep.name, version));
}
}
}
Ok(resolved_versions)
}
pub fn find_cargo_lock<P: AsRef<Path>>(start_dir: P) -> Result<PathBuf> {
let start_dir = start_dir.as_ref();
let mut current_dir = start_dir.to_path_buf();
let cargo_lock = current_dir.join("Cargo.lock");
if cargo_lock.exists() {
return Ok(cargo_lock);
}
while let Some(parent) = current_dir.parent() {
current_dir = parent.to_path_buf();
let cargo_lock = current_dir.join("Cargo.lock");
if cargo_lock.exists() {
return Ok(cargo_lock);
}
}
Err(anyhow::anyhow!(
"Cargo.lock file not found in current directory or its parents"
))
}
pub fn resolve_registry_path() -> Result<PathBuf> {
let home_dir =
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
let registry_dir = home_dir.join(".cargo").join("registry").join("src");
if !registry_dir.exists() {
return Err(anyhow::anyhow!(
"Cargo registry directory not found: {}",
registry_dir.display()
));
}
let entries = fs::read_dir(®istry_dir).with_context(|| {
format!(
"Failed to read cargo registry directory: {}",
registry_dir.display()
)
})?;
let mut latest_dir: Option<(PathBuf, SystemTime)> = None;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Ok(metadata) = fs::metadata(&path) {
if let Ok(modified) = metadata.modified() {
match &latest_dir {
Some((_, latest_modified)) if modified > *latest_modified => {
latest_dir = Some((path, modified));
}
None => {
latest_dir = Some((path, modified));
}
_ => {}
}
}
}
}
}
match latest_dir {
Some((dir, _)) => Ok(dir),
None => Err(anyhow::anyhow!(
"No registry directories found in: {}",
registry_dir.display()
)),
}
}
pub fn construct_dependency_path(
registry_path: &Path,
dependency_name: &str,
dependency_version: &str,
) -> PathBuf {
registry_path.join(format!("{}-{}", dependency_name, dependency_version))
}
pub fn is_dependency_available(registry_path: &Path, dependency: &Dependency) -> bool {
dependency.is_available_in_registry(registry_path)
}
pub fn is_dependency_available_by_parts(
registry_path: &Path,
dependency_name: &str,
dependency_version: &str,
) -> bool {
let dependency = Dependency::new(dependency_name, dependency_version);
is_dependency_available(registry_path, &dependency)
}
pub fn resolve_dependency_paths(
dependencies: &HashMap<String, String>,
) -> Result<HashMap<String, PathBuf>> {
let registry_path = resolve_registry_path()?;
let mut dependency_paths = HashMap::new();
for (name, version) in dependencies {
let dependency_path = construct_dependency_path(®istry_path, name, version);
if dependency_path.exists() && dependency_path.is_dir() {
dependency_paths.insert(name.clone(), dependency_path);
}
}
Ok(dependency_paths)
}
pub fn generate_code_bank(
source_path: &Path,
output_dir: &Path,
dependency_name: &str,
) -> Result<PathBuf> {
if !source_path.exists() || !source_path.is_dir() {
return Err(anyhow::anyhow!(
"Source path does not exist or is not a directory: {}",
source_path.display()
));
}
if !output_dir.exists() {
fs::create_dir_all(output_dir).with_context(|| {
format!(
"Failed to create output directory: {}",
output_dir.display()
)
})?;
}
let output_file = output_dir.join(format!("{}.md", dependency_name));
let code_bank = CodeBank::try_new().with_context(|| "Failed to create CodeBank instance")?;
let ignore_dirs = vec![
"examples".to_string(),
"tests".to_string(),
"benches".to_string(),
];
let config = BankConfig::new(source_path, BankStrategy::Summary, ignore_dirs);
let content = code_bank.generate(&config).with_context(|| {
format!(
"Failed to generate code bank for: {}",
source_path.display()
)
})?;
fs::write(&output_file, content).with_context(|| {
format!(
"Failed to write code bank to file: {}",
output_file.display()
)
})?;
Ok(output_file)
}
pub fn generate_all_code_banks(
dependencies: &DependencyCollection,
registry_path: &Path,
output_dir: &Path,
) -> Result<HashMap<String, PathBuf>> {
let mut code_bank_files = HashMap::new();
let mut errors = Vec::new();
for dependency in dependencies.as_slice() {
let dependency_path = dependency.get_registry_path(registry_path);
if dependency_path.exists() && dependency_path.is_dir() {
match generate_code_bank(&dependency_path, output_dir, &dependency.name) {
Ok(code_bank_file) => {
code_bank_files.insert(dependency.name.clone(), code_bank_file);
}
Err(e) => {
errors.push(format!(
"Failed to generate code bank for {}: {}",
dependency.name, e
));
}
}
} else {
errors.push(format!(
"Dependency not found: {}",
dependency_path.display()
));
}
}
if !errors.is_empty() {
for error in &errors {
eprintln!("Warning: {}", error);
}
}
Ok(code_bank_files)
}
pub fn calculate_tokens(text: &str) -> Result<usize> {
let tokenizer = Tokenizer::from_pretrained("bert-base-cased", None)
.map_err(|e| anyhow::anyhow!("Failed to load tokenizer: {}", e))?;
let encoding = tokenizer
.encode(text, false)
.map_err(|e| anyhow::anyhow!("Failed to tokenize text: {}", e))?;
Ok(encoding.get_tokens().len())
}
pub fn calculate_file_tokens(file_path: &Path) -> Result<usize> {
let content = fs::read_to_string(file_path)
.with_context(|| format!("Failed to read file: {}", file_path.display()))?;
calculate_tokens(&content)
}
#[derive(Debug)]
pub struct FileStats {
pub path: PathBuf,
pub size_bytes: usize,
pub token_count: usize,
}
pub fn calculate_directory_tokens(
dir_path: &Path,
extension: Option<&str>,
) -> Result<HashMap<String, FileStats>> {
let mut file_stats = HashMap::new();
if !dir_path.exists() || !dir_path.is_dir() {
return Err(anyhow::anyhow!(
"Directory does not exist or is not a directory: {}",
dir_path.display()
));
}
for entry in fs::read_dir(dir_path)
.with_context(|| format!("Failed to read directory: {}", dir_path.display()))?
{
let entry = entry?;
let path = entry.path();
if path.is_dir() {
continue;
}
if let Some(ext) = extension {
#[allow(clippy::nonminimal_bool)]
if !path.extension().is_some_and(|e| e == ext) {
continue;
}
}
let file_name = path
.file_stem()
.and_then(|name| name.to_str())
.unwrap_or_default()
.to_string();
let metadata = fs::metadata(&path)?;
let size_bytes = metadata.len() as usize;
let token_count = calculate_file_tokens(&path)?;
let stats = FileStats {
path: path.clone(),
size_bytes,
token_count,
};
file_stats.insert(file_name, stats);
}
Ok(file_stats)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn test_find_cargo_toml_files() -> Result<()> {
let temp_dir = tempdir()?;
let temp_path = temp_dir.path();
let root_cargo_toml = temp_path.join("Cargo.toml");
File::create(&root_cargo_toml)?
.write_all(b"[package]\nname = \"test\"\nversion = \"0.1.0\"\n")?;
let sub_dir = temp_path.join("sub_project");
fs::create_dir(&sub_dir)?;
let sub_cargo_toml = sub_dir.join("Cargo.toml");
File::create(&sub_cargo_toml)?
.write_all(b"[package]\nname = \"sub_test\"\nversion = \"0.1.0\"\n")?;
let nested_dir = sub_dir.join("nested_project");
fs::create_dir(&nested_dir)?;
let nested_cargo_toml = nested_dir.join("Cargo.toml");
File::create(&nested_cargo_toml)?
.write_all(b"[package]\nname = \"nested_test\"\nversion = \"0.1.0\"\n")?;
let hidden_dir = temp_path.join(".hidden");
fs::create_dir(&hidden_dir)?;
let hidden_cargo_toml = hidden_dir.join("Cargo.toml");
File::create(&hidden_cargo_toml)?
.write_all(b"[package]\nname = \"hidden_test\"\nversion = \"0.1.0\"\n")?;
let cargo_toml_files = find_cargo_toml_files(temp_path)?;
assert_eq!(cargo_toml_files.len(), 3);
assert!(cargo_toml_files.contains(&root_cargo_toml));
assert!(cargo_toml_files.contains(&sub_cargo_toml));
assert!(cargo_toml_files.contains(&nested_cargo_toml));
assert!(!cargo_toml_files.contains(&hidden_cargo_toml));
Ok(())
}
#[test]
fn test_find_cargo_toml_files_nonexistent_dir() {
let result = find_cargo_toml_files(Path::new("/nonexistent/directory"));
assert!(result.is_err());
}
#[test]
fn test_find_cargo_toml_files_file_as_dir() -> Result<()> {
let temp_dir = tempdir()?;
let temp_file = temp_dir.path().join("file.txt");
File::create(&temp_file)?.write_all(b"content")?;
let result = find_cargo_toml_files(&temp_file);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_collect_dependencies() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_toml_path = temp_dir.path().join("Cargo.toml");
let cargo_toml_content = r#"
[package]
name = "test-project"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
[dev-dependencies]
tempfile = "3.0"
[build-dependencies]
cc = "1.0"
"#;
File::create(&cargo_toml_path)?.write_all(cargo_toml_content.as_bytes())?;
let dependencies = collect_dependencies(&[cargo_toml_path])?;
assert!(dependencies.contains("anyhow"));
assert!(dependencies.contains("serde"));
assert!(dependencies.contains("tokio"));
assert!(dependencies.contains("tempfile"));
assert!(dependencies.contains("cc"));
assert_eq!(dependencies.len(), 5);
Ok(())
}
#[test]
fn test_extract_dependency_info() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_toml_path = temp_dir.path().join("Cargo.toml");
let cargo_toml_content = r#"
[package]
name = "test-project"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
[dev-dependencies]
tempfile = "3.0"
[build-dependencies]
cc = "1.0"
"#;
File::create(&cargo_toml_path)?.write_all(cargo_toml_content.as_bytes())?;
let dependency_info = extract_dependency_info(&cargo_toml_path)?;
let dep1 = dependency_info.get("anyhow").unwrap();
assert_eq!(dep1.version, "1.0");
let dep2 = dependency_info.get("serde").unwrap();
assert_eq!(dep2.version, "1.0");
let dep3 = dependency_info.get("tokio").unwrap();
assert_eq!(dep3.version, "1.0");
let dep4 = dependency_info.get("tempfile").unwrap();
assert_eq!(dep4.version, "3.0");
let dep5 = dependency_info.get("cc").unwrap();
assert_eq!(dep5.version, "1.0");
assert_eq!(dependency_info.len(), 5);
Ok(())
}
#[test]
fn test_resolve_dependency_versions() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_lock_path = temp_dir.path().join("Cargo.lock");
let cargo_lock_content = r#"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "serde"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
[[package]]
name = "toml"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
"#;
File::create(&cargo_lock_path)?.write_all(cargo_lock_content.as_bytes())?;
let mut dependencies = DependencyCollection::new();
dependencies.add(Dependency::new("anyhow", "1.0.75"));
dependencies.add(Dependency::new("serde", "1.0.150"));
dependencies.add(Dependency::new("toml", "0.8.4"));
dependencies.add(Dependency::new("nonexistent", "1.0"));
let resolved = resolve_dependency_versions(cargo_lock_path, &dependencies)?;
let dep1 = resolved.get("anyhow").unwrap();
assert_eq!(dep1.version, "1.0.75");
let dep2 = resolved.get("serde").unwrap();
assert_eq!(dep2.version, "1.0.150");
let dep3 = resolved.get("toml").unwrap();
assert_eq!(dep3.version, "0.8.4");
assert!(!resolved.contains_name("nonexistent"));
Ok(())
}
#[test]
fn test_find_cargo_lock() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_lock_path = temp_dir.path().join("Cargo.lock");
File::create(&cargo_lock_path)?;
let found_path = find_cargo_lock(temp_dir.path())?;
assert_eq!(found_path, cargo_lock_path);
let sub_dir = temp_dir.path().join("subdir");
fs::create_dir(&sub_dir)?;
let found_from_subdir = find_cargo_lock(&sub_dir)?;
assert_eq!(found_from_subdir, cargo_lock_path);
Ok(())
}
#[test]
fn test_resolve_dependency_versions_nonexistent_file() {
let result = resolve_dependency_versions(
Path::new("/nonexistent/Cargo.lock"),
&DependencyCollection::new(),
);
assert!(result.is_err());
}
#[test]
fn test_construct_dependency_path() {
let registry_path = Path::new("/home/user/.cargo/registry/src/index.crates.io-12345");
let dependency_name = "serde";
let dependency_version = "1.0.150";
let expected_path =
PathBuf::from("/home/user/.cargo/registry/src/index.crates.io-12345/serde-1.0.150");
let actual_path =
construct_dependency_path(registry_path, dependency_name, dependency_version);
assert_eq!(actual_path, expected_path);
}
#[test]
fn test_resolve_registry_path_with_mock() -> Result<()> {
let temp_dir = tempdir()?;
let mock_home = temp_dir.path();
let registry_src = mock_home.join(".cargo").join("registry").join("src");
fs::create_dir_all(®istry_src)?;
let old_registry = registry_src.join("index.crates.io-old");
let new_registry = registry_src.join("index.crates.io-new");
fs::create_dir(&old_registry)?;
std::thread::sleep(std::time::Duration::from_millis(10));
fs::create_dir(&new_registry)?;
assert!(new_registry.exists());
assert!(old_registry.exists());
Ok(())
}
#[test]
fn test_generate_code_bank() -> Result<()> {
let temp_dir = tempdir()?;
let source_dir = temp_dir.path().join("source");
let output_dir = temp_dir.path().join("output");
fs::create_dir_all(&source_dir)?;
let source_file = source_dir.join("lib.rs");
let source_content = r#"
/// This is a test function.
pub fn test_function() -> String {
"Hello, world!".to_string()
}
"#;
fs::write(&source_file, source_content)?;
let code_bank_file = generate_code_bank(&source_dir, &output_dir, "test_dependency")?;
assert!(code_bank_file.exists());
let content = fs::read_to_string(&code_bank_file)?;
assert!(!content.is_empty());
Ok(())
}
#[test]
fn test_calculate_tokens() -> Result<()> {
let text = "Hello, world! This is a test.";
let token_count = calculate_tokens(text)?;
assert!(token_count > 0);
assert!(token_count < 20);
Ok(())
}
#[test]
fn test_calculate_file_tokens() -> Result<()> {
let temp_dir = tempdir()?;
let file_path = temp_dir.path().join("test.txt");
let content =
"This is a test file.\nIt has multiple lines.\nEach line should be tokenized.";
File::create(&file_path)?.write_all(content.as_bytes())?;
let token_count = calculate_file_tokens(&file_path)?;
assert!(token_count > 0);
assert!(token_count < 50);
Ok(())
}
#[test]
fn test_calculate_directory_tokens() -> Result<()> {
let temp_dir = tempdir()?;
let file1_path = temp_dir.path().join("file1.md");
let file2_path = temp_dir.path().join("file2.md");
let file3_path = temp_dir.path().join("file3.txt");
File::create(&file1_path)?.write_all(b"This is file 1.")?;
File::create(&file2_path)?.write_all(b"This is file 2. It has more content.")?;
File::create(&file3_path)?.write_all(b"This is file 3.")?;
let stats = calculate_directory_tokens(temp_dir.path(), Some("md"))?;
assert_eq!(stats.len(), 2);
assert!(stats.contains_key("file1"));
assert!(stats.contains_key("file2"));
assert!(!stats.contains_key("file3"));
for file_stat in stats.values() {
assert!(file_stat.size_bytes > 0);
assert!(file_stat.token_count > 0);
}
let all_stats = calculate_directory_tokens(temp_dir.path(), None)?;
assert_eq!(all_stats.len(), 3);
Ok(())
}
#[test]
fn test_extract_dependency_info_with_all_dependency_types() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_toml_path = temp_dir.path().join("Cargo.toml");
let cargo_toml_content = r#"
[package]
name = "test_package"
version = "0.1.0"
edition = "2021"
[dependencies]
simple_dep = "1.0"
detailed_dep = { version = "2.0", features = ["full"] }
[dev-dependencies]
test_dep = "0.5"
[build-dependencies]
build_dep = { version = "0.3", optional = true }
"#;
File::create(&cargo_toml_path)?.write_all(cargo_toml_content.as_bytes())?;
let dependency_info = extract_dependency_info(&cargo_toml_path)?;
assert_eq!(dependency_info.len(), 4);
let simple_dep = dependency_info.get("simple_dep").unwrap();
assert_eq!(simple_dep.version, "1.0");
let detailed_dep = dependency_info.get("detailed_dep").unwrap();
assert_eq!(detailed_dep.version, "2.0");
let test_dep = dependency_info.get("test_dep").unwrap();
assert_eq!(test_dep.version, "0.5");
let build_dep = dependency_info.get("build_dep").unwrap();
assert_eq!(build_dep.version, "0.3");
Ok(())
}
#[test]
fn test_resolve_dependency_versions_with_multiple_versions() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_lock_path = temp_dir.path().join("Cargo.lock");
let cargo_lock_content = r#"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "dep1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dep1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dep2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#;
File::create(&cargo_lock_path)?.write_all(cargo_lock_content.as_bytes())?;
let mut dependencies = DependencyCollection::new();
dependencies.add(Dependency::new("dep1", "1.0"));
dependencies.add(Dependency::new("dep2", "0.5"));
let resolved = resolve_dependency_versions(cargo_lock_path, &dependencies)?;
assert_eq!(resolved.len(), 2);
let dep1 = resolved.get("dep1").unwrap();
assert_eq!(dep1.version, "1.2.0");
let dep2 = resolved.get("dep2").unwrap();
assert_eq!(dep2.version, "0.5.0");
Ok(())
}
#[test]
fn test_collect_dependencies_with_overlapping_deps() -> Result<()> {
let temp_dir = tempdir()?;
let cargo_toml1_path = temp_dir.path().join("Cargo1.toml");
let cargo_toml1_content = r#"
[package]
name = "test1"
version = "0.1.0"
[dependencies]
dep1 = "1.0"
dep2 = "0.5"
"#;
File::create(&cargo_toml1_path)?.write_all(cargo_toml1_content.as_bytes())?;
let cargo_toml2_path = temp_dir.path().join("Cargo2.toml");
let cargo_toml2_content = r#"
[package]
name = "test2"
version = "0.1.0"
[dependencies]
dep2 = "0.6"
dep3 = "1.5"
"#;
File::create(&cargo_toml2_path)?.write_all(cargo_toml2_content.as_bytes())?;
let cargo_toml_files = vec![cargo_toml1_path, cargo_toml2_path];
let dependencies = collect_dependencies(&cargo_toml_files)?;
assert_eq!(dependencies.len(), 3);
assert!(dependencies.contains("dep1"));
assert!(dependencies.contains("dep2"));
assert!(dependencies.contains("dep3"));
Ok(())
}
#[test]
fn test_is_dependency_available() {
let registry_path = Path::new("/non/existent/path");
assert!(!is_dependency_available(
registry_path,
&Dependency::new("some-dep", "1.0.0")
));
}
}