Skip to main content

vtcode_commons/
utils.rs

1//! Generic utility functions
2
3use anyhow::Result;
4use regex::Regex;
5use sha2::{Digest, Sha256};
6use std::time::{SystemTime, UNIX_EPOCH};
7
8/// Get current Unix timestamp in seconds
9#[inline]
10pub fn current_timestamp() -> u64 {
11    SystemTime::now()
12        .duration_since(UNIX_EPOCH)
13        .expect("System time is before Unix epoch")
14        .as_secs()
15}
16
17/// Calculate SHA256 hash of the given content
18pub fn calculate_sha256(content: &[u8]) -> String {
19    let mut hasher = Sha256::new();
20    hasher.update(content);
21    format!("{:x}", hasher.finalize())
22}
23
24/// Extract a string value from a simple TOML key assignment within [package]
25pub fn extract_toml_str(content: &str, key: &str) -> Option<String> {
26    // Only consider the [package] section to avoid matching other tables
27    let pkg_section = if let Some(start) = content.find("[package]") {
28        let rest = &content[start + "[package]".len()..];
29        // Stop at next section header or end
30        if let Some(_next) = rest.find('\n') {
31            &content[start..]
32        } else {
33            &content[start..]
34        }
35    } else {
36        content
37    };
38
39    // Example target: name = "vtcode"
40    let pattern = format!(r#"(?m)^\s*{}\s*=\s*"([^"]+)"\s*$"#, regex::escape(key));
41    let re = Regex::new(&pattern).ok()?;
42    re.captures(pkg_section)
43        .and_then(|caps| caps.get(1).map(|m| m.as_str().to_owned()))
44}
45
46/// Get the first meaningful section of the README/markdown as an excerpt
47pub fn extract_readme_excerpt(md: &str, max_len: usize) -> String {
48    // Take from start until we pass the first major sections or hit max_len
49    let mut excerpt = String::new();
50    for line in md.lines() {
51        // Stop if we reach a deep section far into the doc
52        if excerpt.len() > max_len {
53            break;
54        }
55        excerpt.push_str(line);
56        excerpt.push('\n');
57        // Prefer stopping after an initial overview section
58        if line.trim().starts_with("## ") && excerpt.len() > (max_len / 2) {
59            break;
60        }
61    }
62    if excerpt.len() > max_len {
63        excerpt.truncate(max_len);
64        excerpt.push_str("...\n");
65    }
66    excerpt
67}
68
69/// Safe text replacement with validation
70pub fn safe_replace_text(content: &str, old_str: &str, new_str: &str) -> Result<String> {
71    if old_str.is_empty() {
72        return Err(anyhow::anyhow!("old_string cannot be empty"));
73    }
74
75    if !content.contains(old_str) {
76        return Err(anyhow::anyhow!("Text '{}' not found in content", old_str));
77    }
78
79    Ok(content.replace(old_str, new_str))
80}