1use anyhow::{Context, Result};
4use regex::Regex;
5use sha2::{Digest, Sha256};
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[inline]
10pub fn current_timestamp() -> u64 {
11 current_timestamp_result().unwrap_or(0)
12}
13
14#[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
23pub fn calculate_sha256(content: &[u8]) -> String {
25 let mut hasher = Sha256::new();
26 hasher.update(content);
27 let digest = hasher.finalize();
28 let mut output = String::with_capacity(digest.len() * 2);
29
30 for byte in digest {
31 output.push(nibble_to_hex(byte >> 4));
32 output.push(nibble_to_hex(byte & 0x0f));
33 }
34
35 output
36}
37
38#[allow(clippy::unreachable)]
39fn nibble_to_hex(nibble: u8) -> char {
40 match nibble {
41 0..=9 => char::from(b'0' + nibble),
42 10..=15 => char::from(b'a' + (nibble - 10)),
43 _ => unreachable!("nibble must be in 0..=15"),
44 }
45}
46
47pub fn extract_toml_str(content: &str, key: &str) -> Option<String> {
49 let pkg_section = if let Some(start) = content.find("[package]") {
51 let rest = &content[start + "[package]".len()..];
52 if let Some(_next) = rest.find('\n') {
54 &content[start..]
55 } else {
56 &content[start..]
57 }
58 } else {
59 content
60 };
61
62 let pattern = format!(r#"(?m)^\s*{}\s*=\s*"([^"]+)"\s*$"#, regex::escape(key));
64 let re = Regex::new(&pattern).ok()?;
65 re.captures(pkg_section)
66 .and_then(|caps| caps.get(1).map(|m| m.as_str().to_owned()))
67}
68
69pub fn extract_readme_excerpt(md: &str, max_len: usize) -> String {
71 let mut excerpt = String::new();
73 for line in md.lines() {
74 if excerpt.len() > max_len {
76 break;
77 }
78 excerpt.push_str(line);
79 excerpt.push('\n');
80 if line.trim().starts_with("## ") && excerpt.len() > (max_len / 2) {
82 break;
83 }
84 }
85 if excerpt.len() > max_len {
86 excerpt.truncate(max_len);
87 excerpt.push_str("...\n");
88 }
89 excerpt
90}
91
92pub fn safe_replace_text(content: &str, old_str: &str, new_str: &str) -> Result<String> {
94 if old_str.is_empty() {
95 return Err(anyhow::anyhow!("old_string cannot be empty"));
96 }
97
98 if !content.contains(old_str) {
99 return Err(anyhow::anyhow!("Text '{}' not found in content", old_str));
100 }
101
102 Ok(content.replace(old_str, new_str))
103}