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
18pub fn hex_encode(input: &[u8]) -> String {
19    hex::encode(input)
20}
21
22pub fn hex_decode_text(input: &str) -> Result<String, String> {
23    hex::decode(input.as_bytes())
24        .map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
25        .map_err(|error| format!("hex decode error: {error}"))
26}
27
28pub fn trim_text(input: &str) -> &str {
29    input.trim()
30}
31
32pub fn replace_text(input: &str, old: &str, new: &str) -> String {
33    input.replace(old, new)
34}
35
36pub fn starts_with_text(input: &str, prefix: &str) -> bool {
37    input.starts_with(prefix)
38}
39
40pub fn ends_with_text(input: &str, suffix: &str) -> bool {
41    input.ends_with(suffix)
42}
43
44pub fn sha256_hex(input: &[u8]) -> String {
45    use sha2::Digest;
46    hex::encode(sha2::Sha256::digest(input))
47}
48
49/// Join host-supplied path segments into Harn's target-independent `/` form.
50/// Absolute POSIX, UNC, or drive-rooted segments reset the accumulated path,
51/// matching `PathBuf::push` without making artifact behavior depend on the
52/// machine that compiled the kernel.
53pub fn join_path_segments<I, S>(segments: I) -> String
54where
55    I: IntoIterator<Item = S>,
56    S: AsRef<str>,
57{
58    let mut joined = String::new();
59    for segment in segments {
60        let segment = segment.as_ref().replace('\\', "/");
61        if segment.is_empty() {
62            continue;
63        }
64        let absolute = segment.starts_with('/')
65            || segment
66                .as_bytes()
67                .get(1..3)
68                .is_some_and(|suffix| suffix == b":/");
69        if absolute {
70            joined = segment;
71            continue;
72        }
73        if !joined.is_empty() && !joined.ends_with('/') {
74            joined.push('/');
75        }
76        joined.push_str(segment.trim_start_matches('/'));
77    }
78    joined
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn hex_text_semantics_cover_control_bytes_and_invalid_input() {
87        assert_eq!(hex_encode(b"\0\x01\x02"), "000102");
88        assert_eq!(hex_decode_text("000102").unwrap(), "\0\x01\x02");
89        assert!(hex_decode_text("abc").is_err());
90    }
91
92    #[test]
93    fn string_primitives_preserve_rust_unicode_semantics() {
94        assert_eq!(trim_text(" \tHarn\n"), "Harn");
95        assert_eq!(replace_text("aλa", "a", "β"), "βλβ");
96        assert!(starts_with_text("Portable Harn", "Portable"));
97        assert!(ends_with_text("Portable Harn", "Harn"));
98        assert_eq!(
99            sha256_hex(b"abc"),
100            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
101        );
102    }
103
104    #[test]
105    fn path_join_is_target_independent_for_posix_windows_and_unc_roots() {
106        assert_eq!(
107            join_path_segments(["/repo", ".harn", "state.json"]),
108            "/repo/.harn/state.json"
109        );
110        assert_eq!(
111            join_path_segments([r"C:\repo", ".harn", "state.json"]),
112            "C:/repo/.harn/state.json"
113        );
114        assert_eq!(
115            join_path_segments([r"\\server\share", "state.json"]),
116            "//server/share/state.json"
117        );
118        assert_eq!(
119            join_path_segments(["ignored", "/absolute", "file"]),
120            "/absolute/file"
121        );
122    }
123}