#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn format_tokens(n: u64) -> String {
if n >= 1_000_000 {
format!("{:.1}M", n as f64 / 1_000_000.0)
} else if n >= 1_000 {
format!("{:.1}k", n as f64 / 1_000.0)
} else {
n.to_string()
}
}
#[must_use]
pub fn truncate_to_bytes(s: &str, max_bytes: usize) -> String {
if s.len() <= max_bytes {
return s.to_owned();
}
let mut byte_count = 0usize;
let mut end = 0usize;
for ch in s.chars() {
let ch_len = ch.len_utf8();
if byte_count + ch_len > max_bytes {
break;
}
byte_count += ch_len;
end += ch_len;
}
s[..end].to_owned()
}
#[must_use]
pub fn truncate_to_bytes_ref(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut end = max_bytes;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
#[must_use]
pub fn estimate_tokens(text: &str) -> usize {
text.chars().count() / 4
}
#[must_use]
pub fn truncate_chars(s: &str, max_chars: usize) -> &str {
if max_chars == 0 {
return "";
}
match s.char_indices().nth(max_chars) {
Some((byte_idx, _)) => &s[..byte_idx],
None => s,
}
}
#[must_use]
pub fn truncate_to_chars(s: &str, max_chars: usize) -> String {
if max_chars == 0 {
return String::new();
}
let count = s.chars().count();
if count <= max_chars {
s.to_owned()
} else {
let truncated: String = s.chars().take(max_chars).collect();
format!("{truncated}\u{2026}")
}
}
#[must_use]
pub fn patch_frontmatter_fields(skill_md: &str, fields: &[(&str, &str)]) -> String {
let Some(after_open) = skill_md.strip_prefix("---") else {
return skill_md.to_string();
};
let Some(close_pos) = after_open.find("---") else {
return skill_md.to_string();
};
let yaml = &after_open[..close_pos];
let rest = &after_open[close_pos..];
let mut lines: Vec<String> = yaml
.lines()
.filter(|l| {
let t = l.trim_start();
!fields
.iter()
.any(|(key, _)| t.starts_with(&format!("{key}:")))
})
.map(str::to_string)
.collect();
let insert_after = lines
.iter()
.position(|l| l.trim_start().starts_with("name:"))
.map_or(lines.len(), |p| p + 1);
for (i, (key, value)) in fields.iter().enumerate() {
lines.insert(insert_after + i, format!("{key}: {value}"));
}
format!(
"---{}\n---{}",
lines.join("\n"),
rest.trim_start_matches("---")
)
}
#[must_use]
pub fn xml_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
other => out.push(other),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_short_unchanged() {
assert_eq!(truncate_to_bytes("hello", 10), "hello");
}
#[test]
fn bytes_exact_unchanged() {
assert_eq!(truncate_to_bytes("hello", 5), "hello");
}
#[test]
fn bytes_truncates_at_boundary() {
let s = "hello world";
assert_eq!(truncate_to_bytes(s, 5), "hello");
}
#[test]
fn bytes_unicode_boundary() {
let s = "hΓ©llo";
assert_eq!(truncate_to_bytes(s, 3), "hΓ©");
}
#[test]
fn bytes_zero_returns_empty() {
assert_eq!(truncate_to_bytes("hello", 0), "");
}
#[test]
fn bytes_ref_short_unchanged() {
assert_eq!(truncate_to_bytes_ref("hello", 10), "hello");
}
#[test]
fn bytes_ref_truncates_at_boundary() {
assert_eq!(truncate_to_bytes_ref("hello world", 5), "hello");
}
#[test]
fn bytes_ref_unicode_boundary() {
let s = "hΓ©llo";
assert_eq!(truncate_to_bytes_ref(s, 2), "h");
}
#[test]
fn chars_short_unchanged() {
assert_eq!(truncate_chars("hello", 10), "hello");
}
#[test]
fn chars_exact_unchanged() {
assert_eq!(truncate_chars("hello", 5), "hello");
}
#[test]
fn chars_truncates_by_char() {
assert_eq!(truncate_chars("hello world", 5), "hello");
}
#[test]
fn chars_zero_returns_empty() {
assert_eq!(truncate_chars("hello", 0), "");
}
#[test]
fn chars_unicode_by_char() {
let s = "πππππextra";
assert_eq!(truncate_chars(s, 5), "πππππ");
}
#[test]
fn to_chars_short_unchanged() {
assert_eq!(truncate_to_chars("hello", 10), "hello");
}
#[test]
fn to_chars_exact_unchanged() {
assert_eq!(truncate_to_chars("hello", 5), "hello");
}
#[test]
fn to_chars_appends_ellipsis() {
assert_eq!(truncate_to_chars("hello world", 5), "hello\u{2026}");
}
#[test]
fn to_chars_zero_returns_empty() {
assert_eq!(truncate_to_chars("hello", 0), "");
}
#[test]
fn to_chars_unicode() {
let s = "πππππextra";
assert_eq!(truncate_to_chars(s, 5), "πππππ\u{2026}");
}
#[test]
fn frontmatter_inserts_after_name() {
let md = "---\nname: foo\ndescription: Foo.\n---\n\n# Body\n";
let patched = patch_frontmatter_fields(md, &[("source", "generator")]);
assert!(
patched.contains("name: foo\nsource: generator\n"),
"patched: {patched}"
);
}
#[test]
fn frontmatter_replaces_existing_values() {
let md = "---\nname: foo\nsource: old\nparent_skill: old-parent\ndescription: Foo.\n---\n\n# Body\n";
let patched =
patch_frontmatter_fields(md, &[("source", "new"), ("parent_skill", "new-parent")]);
assert_eq!(patched.matches("source:").count(), 1);
assert!(patched.contains("source: new"));
assert!(patched.contains("parent_skill: new-parent"));
assert!(!patched.contains("old-parent"));
}
#[test]
fn frontmatter_no_delimiters_unchanged() {
let md = "# No frontmatter\n\nbody";
assert_eq!(patch_frontmatter_fields(md, &[("source", "x")]), md);
}
#[test]
fn frontmatter_no_name_line_appends_at_end() {
let md = "---\ndescription: Foo.\n---\n\n# Body\n";
let patched = patch_frontmatter_fields(md, &[("source", "generator")]);
assert!(
patched.contains("description: Foo.\nsource: generator\n---"),
"patched: {patched}"
);
}
#[test]
fn frontmatter_missing_closing_delimiter_unchanged() {
let md = "---\nname: foo\ndescription: Foo.\n\nbody without closing delimiter";
assert_eq!(patch_frontmatter_fields(md, &[("source", "x")]), md);
}
#[test]
fn frontmatter_closing_delimiter_preceded_by_newline() {
let md = "---\nname: foo\ndescription: Foo.\n---\n\n# Body\n";
let patched =
patch_frontmatter_fields(md, &[("source", "generator"), ("parent_skill", "bar")]);
assert!(
patched.contains("Foo.\n---"),
"closing delimiter not preceded by newline: {patched}"
);
assert!(!patched.contains("Foo.---"), "patched: {patched}");
}
}