Skip to main content

vtcode_commons/
utils.rs

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