Skip to main content

harn_kernel/
pure.rs

1//! Small authority-free primitives shared by native and portable runtimes.
2//!
3//! These functions are the semantic implementation beneath runtime adapters;
4//! they do not expose VM values, host handles, or target-specific state.
5
6mod regex;
7mod secret_scan;
8
9pub use regex::{
10    regex_captures, regex_matches, regex_replace, regex_split, RegexCapture,
11    MAX_REGEX_PATTERN_BYTES,
12};
13pub use secret_scan::{
14    compiled_secret_patterns, scan_secrets, secret_patterns_compiled, CompiledSecretPattern,
15    SecretFinding,
16};
17
18/// Select existing fields without changing their values. Lookup belongs to
19/// the value adapter so records, structs, and Harness handles use one rule.
20pub fn pick_fields<'a, V>(
21    keys: impl IntoIterator<Item = &'a str>,
22    mut lookup: impl FnMut(&str) -> Option<V>,
23) -> std::collections::BTreeMap<String, V> {
24    let mut selected = std::collections::BTreeMap::new();
25    for key in keys {
26        if !selected.contains_key(key) {
27            if let Some(value) = lookup(key) {
28                selected.insert(key.to_owned(), value);
29            }
30        }
31    }
32    selected
33}
34
35pub fn hex_encode(input: &[u8]) -> String {
36    hex::encode(input)
37}
38
39pub fn hex_decode_text(input: &str) -> Result<String, String> {
40    hex::decode(input.as_bytes())
41        .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
42        .map_err(|error| format!("hex decode error: {error}"))
43}
44
45pub fn trim_text(input: &str) -> &str {
46    input.trim()
47}
48
49pub fn replace_text(input: &str, old: &str, new: &str) -> String {
50    input.replace(old, new)
51}
52
53pub fn starts_with_text(input: &str, prefix: &str) -> bool {
54    input.starts_with(prefix)
55}
56
57pub fn ends_with_text(input: &str, suffix: &str) -> bool {
58    input.ends_with(suffix)
59}
60
61pub fn sha256_hex(input: &[u8]) -> String {
62    use sha2::Digest;
63    hex::encode(sha2::Sha256::digest(input))
64}
65
66/// Join host-supplied path segments into Harn's target-independent `/` form.
67/// Absolute POSIX, UNC, or drive-rooted segments reset the accumulated path,
68/// matching `PathBuf::push` without making artifact behavior depend on the
69/// machine that compiled the kernel.
70pub fn join_path_segments<I, S>(segments: I) -> String
71where
72    I: IntoIterator<Item = S>,
73    S: AsRef<str>,
74{
75    let mut joined = String::new();
76    for segment in segments {
77        let segment = segment.as_ref().replace('\\', "/");
78        if segment.is_empty() {
79            continue;
80        }
81        let absolute = segment.starts_with('/')
82            || segment
83                .as_bytes()
84                .get(1..3)
85                .is_some_and(|suffix| suffix == b":/");
86        if absolute {
87            joined = segment;
88            continue;
89        }
90        if !joined.is_empty() && !joined.ends_with('/') {
91            joined.push('/');
92        }
93        joined.push_str(segment.trim_start_matches('/'));
94    }
95    joined
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn hex_text_semantics_cover_control_bytes_and_invalid_input() {
104        assert_eq!(hex_encode(b"\0\x01\x02"), "000102");
105        assert_eq!(hex_decode_text("000102").unwrap(), "\0\x01\x02");
106        assert!(hex_decode_text("abc").is_err());
107    }
108
109    #[test]
110    fn string_primitives_preserve_rust_unicode_semantics() {
111        assert_eq!(trim_text(" \tHarn\n"), "Harn");
112        assert_eq!(replace_text("aλa", "a", "β"), "βλβ");
113        assert!(starts_with_text("Portable Harn", "Portable"));
114        assert!(ends_with_text("Portable Harn", "Harn"));
115        assert_eq!(
116            sha256_hex(b"abc"),
117            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
118        );
119    }
120
121    #[test]
122    fn path_join_is_target_independent_for_posix_windows_and_unc_roots() {
123        assert_eq!(
124            join_path_segments(["/repo", ".harn", "state.json"]),
125            "/repo/.harn/state.json"
126        );
127        assert_eq!(
128            join_path_segments([r"C:\repo", ".harn", "state.json"]),
129            "C:/repo/.harn/state.json"
130        );
131        assert_eq!(
132            join_path_segments([r"\\server\share", "state.json"]),
133            "//server/share/state.json"
134        );
135        assert_eq!(
136            join_path_segments(["ignored", "/absolute", "file"]),
137            "/absolute/file"
138        );
139    }
140}