feat: publish HoloLake model-native living system source

This commit is contained in:
冰朔 2026-08-03 10:04:41 +08:00
commit c395dd3a99
2467 changed files with 615073 additions and 0 deletions

View file

@ -0,0 +1,184 @@
#[derive(Clone, Copy)]
pub(crate) struct FrontmatterKeyRule {
read_key: &'static str,
write_key: &'static str,
aliases: &'static [&'static str],
canonicalize_on_write: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct FrontmatterKey<'a>(&'a str);
impl<'a> FrontmatterKey<'a> {
pub(crate) fn new(key: &'a str) -> Self {
Self(key)
}
pub(crate) fn normalized(self) -> String {
self.0.trim().to_ascii_lowercase().replace(' ', "_")
}
pub(crate) fn is_reserved(self) -> bool {
self.normalized().starts_with('_') || is_known_frontmatter_key(self)
}
}
const KNOWN_FRONTMATTER_KEYS: &[FrontmatterKeyRule] = &[
FrontmatterKeyRule {
read_key: "title",
write_key: "title",
aliases: &["title"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "type",
write_key: "type",
aliases: &["type", "is_a", "Is A"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "aliases",
write_key: "aliases",
aliases: &["aliases"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_archived",
write_key: "_archived",
aliases: &["_archived", "Archived", "archived"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "Status",
write_key: "Status",
aliases: &["Status", "status"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_icon",
write_key: "_icon",
aliases: &["_icon", "icon"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "color",
write_key: "color",
aliases: &["color"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_order",
write_key: "_order",
aliases: &["_order", "order"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "_sidebar_label",
write_key: "_sidebar_label",
aliases: &["_sidebar_label", "sidebar_label", "sidebar label"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "template",
write_key: "template",
aliases: &["template"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_sort",
write_key: "_sort",
aliases: &["_sort", "sort"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "view",
write_key: "view",
aliases: &["view"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_width",
write_key: "_width",
aliases: &["_width", "width"],
canonicalize_on_write: true,
},
FrontmatterKeyRule {
read_key: "_display",
write_key: "_display",
aliases: &["_display"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "visible",
write_key: "visible",
aliases: &["visible"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_organized",
write_key: "_organized",
aliases: &["_organized"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_favorite",
write_key: "_favorite",
aliases: &["_favorite"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_favorite_index",
write_key: "_favorite_index",
aliases: &["_favorite_index"],
canonicalize_on_write: false,
},
FrontmatterKeyRule {
read_key: "_list_properties_display",
write_key: "_list_properties_display",
aliases: &["_list_properties_display"],
canonicalize_on_write: false,
},
];
impl FrontmatterKeyRule {
pub(crate) fn read_key(self) -> &'static str {
self.read_key
}
pub(crate) fn write_key(self) -> &'static str {
self.write_key
}
pub(crate) fn canonicalizes_on_write(self) -> bool {
self.canonicalize_on_write
}
fn matches(self, key: FrontmatterKey<'_>) -> bool {
let normalized = key.normalized();
self.aliases
.iter()
.any(|alias| FrontmatterKey::new(alias).normalized() == normalized)
}
}
pub(crate) fn frontmatter_key_rule(key: FrontmatterKey<'_>) -> Option<FrontmatterKeyRule> {
KNOWN_FRONTMATTER_KEYS
.iter()
.copied()
.find(|rule| rule.matches(key))
}
pub(crate) fn canonical_known_frontmatter_key(key: FrontmatterKey<'_>) -> Option<&'static str> {
frontmatter_key_rule(key).map(FrontmatterKeyRule::read_key)
}
pub(crate) fn frontmatter_keys_match(left: FrontmatterKey<'_>, right: FrontmatterKey<'_>) -> bool {
match (frontmatter_key_rule(left), frontmatter_key_rule(right)) {
(Some(left_rule), Some(right_rule)) => left_rule.read_key() == right_rule.read_key(),
_ => left.normalized() == right.normalized(),
}
}
pub(crate) fn is_known_frontmatter_key(key: FrontmatterKey<'_>) -> bool {
frontmatter_key_rule(key).is_some()
}

View file

@ -0,0 +1,254 @@
pub(crate) mod keys;
mod ops;
#[cfg(test)]
mod ops_update_tests;
mod yaml;
use std::fs;
use std::path::Path;
pub use ops::update_frontmatter_content;
pub use yaml::{format_yaml_key, FrontmatterValue};
fn is_markdown_path(path: &Path) -> bool {
path.extension()
.and_then(|extension| extension.to_str())
.is_some_and(|extension| {
extension.eq_ignore_ascii_case("md") || extension.eq_ignore_ascii_case("markdown")
})
}
fn validate_frontmatter_path(path: &str, file_path: &Path) -> Result<(), String> {
if !file_path.exists() {
return Err(format!("File does not exist: {}", path));
}
if !is_markdown_path(file_path) {
return Err(format!(
"Frontmatter can only be updated on Markdown notes: {}",
path
));
}
Ok(())
}
/// Helper to read a file, apply a frontmatter transformation, and write back.
pub fn with_frontmatter<F>(path: &str, transform: F) -> Result<String, String>
where
F: FnOnce(&str) -> Result<String, String>,
{
let file_path = Path::new(path);
validate_frontmatter_path(path, file_path)?;
let content =
fs::read_to_string(file_path).map_err(|e| format!("Failed to read {}: {}", path, e))?;
let updated = transform(&content)?;
fs::write(file_path, &updated).map_err(|e| format!("Failed to write {}: {}", path, e))?;
Ok(updated)
}
/// Update a single frontmatter property in a markdown file.
pub fn update_frontmatter(
path: &str,
key: &str,
value: FrontmatterValue,
) -> Result<String, String> {
with_frontmatter(path, |content| {
update_frontmatter_content(content, key, Some(value.clone()))
})
}
/// Delete a frontmatter property from a markdown file.
pub fn delete_frontmatter_property(path: &str, key: &str) -> Result<String, String> {
with_frontmatter(path, |content| {
update_frontmatter_content(content, key, None)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_frontmatter_file_not_found() {
let result = with_frontmatter("/nonexistent/path/file.md", |c| Ok(c.to_string()));
assert!(result.is_err());
assert!(result.unwrap_err().contains("does not exist"));
}
#[test]
fn test_update_frontmatter_rejects_binary_attachment_before_utf8_read() {
let dir = tempfile::tempdir().unwrap();
let attachment_dir = dir.path().join("attachments");
fs::create_dir_all(&attachment_dir).unwrap();
let attachment_path = attachment_dir.join("screenshot.png");
fs::write(&attachment_path, [0xff, 0xfe, 0xfd]).unwrap();
let err = update_frontmatter(
attachment_path.to_str().unwrap(),
"Status",
FrontmatterValue::String("Done".to_string()),
)
.unwrap_err();
assert!(err.contains("Frontmatter can only be updated on Markdown notes"));
assert!(err.contains("screenshot.png"));
}
#[test]
fn test_roundtrip_update_string() {
let content = "---\nStatus: Draft\n---\n# Test\n";
let updated = update_frontmatter_content(
content,
"Status",
Some(FrontmatterValue::String("Active".to_string())),
)
.unwrap();
let matter = gray_matter::Matter::<gray_matter::engine::YAML>::new();
let parsed = matter.parse(&updated);
let data = parsed.data.unwrap();
if let gray_matter::Pod::Hash(map) = data {
assert_eq!(map.get("Status").unwrap().as_string().unwrap(), "Active");
} else {
panic!("Expected hash");
}
}
#[test]
fn test_roundtrip_update_list() {
let content = "---\nStatus: Draft\n---\n# Test\n";
let updated = update_frontmatter_content(
content,
"aliases",
Some(FrontmatterValue::List(vec![
"A".to_string(),
"B".to_string(),
])),
)
.unwrap();
let matter = gray_matter::Matter::<gray_matter::engine::YAML>::new();
let parsed = matter.parse(&updated);
let data = parsed.data.unwrap();
if let gray_matter::Pod::Hash(map) = data {
let aliases = map.get("aliases").unwrap();
if let gray_matter::Pod::Array(arr) = aliases {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0].as_string().unwrap(), "A");
assert_eq!(arr[1].as_string().unwrap(), "B");
} else {
panic!("Expected array");
}
} else {
panic!("Expected hash");
}
}
#[test]
fn test_roundtrip_add_then_delete() {
let content = "---\nStatus: Draft\n---\n# Test\n";
let with_owner = update_frontmatter_content(
content,
"Owner",
Some(FrontmatterValue::String("Luca".to_string())),
)
.unwrap();
assert!(with_owner.contains("Owner: Luca"));
let without_owner = update_frontmatter_content(&with_owner, "Owner", None).unwrap();
assert!(!without_owner.contains("Owner"));
assert!(without_owner.contains("Status: Draft"));
}
#[test]
fn test_update_frontmatter_empty_block() {
let content = "---\n---\n\n# Test\n";
let result = update_frontmatter_content(
content,
"title",
Some(FrontmatterValue::String("New Title".to_string())),
);
assert!(result.is_ok());
assert!(result.unwrap().contains("title: New Title"));
}
#[test]
fn test_update_frontmatter_block_scalar_writes_and_rewrites() {
let cases = [
(
"---\ntype: Type\n---\n# Project\n",
"## Objective\n\n## Timeline",
&["template: |", " ## Objective", "type: Type"][..],
&[][..],
),
(
"---\ntype: Type\ntemplate: |\n ## Old\n \n ## Stuff\ncolor: green\n---\n# Project\n",
"## New\n\n## Content",
&[" ## New", "color: green"][..],
&["## Old"][..],
),
];
for (content, template, expected_present, expected_absent) in cases {
let updated = update_frontmatter_content(
content,
"template",
Some(FrontmatterValue::String(template.to_string())),
)
.unwrap();
for expected in expected_present {
assert!(updated.contains(expected));
}
for unexpected in expected_absent {
assert!(!updated.contains(unexpected));
}
}
}
#[test]
fn test_delete_frontmatter_block_scalar() {
let content =
"---\ntype: Type\ntemplate: |\n ## Heading\n \n ## Body\ncolor: green\n---\n# Project\n";
let updated = update_frontmatter_content(content, "template", None).unwrap();
assert!(!updated.contains("template"));
assert!(updated.contains("color: green"));
}
#[test]
fn test_update_frontmatter_no_body_after_closing() {
let content = "---\ntitle: Old\n---\n";
let updated = update_frontmatter_content(
content,
"title",
Some(FrontmatterValue::String("New".to_string())),
)
.unwrap();
assert!(updated.contains("title: New"));
assert!(!updated.contains("title: Old"));
}
#[test]
fn test_roundtrip_block_scalar() {
let content = "---\ntype: Type\n---\n# Project\n";
let template = "## Objective\n\nDescribe the goal.\n\n## Timeline\n\nKey dates.";
let updated = update_frontmatter_content(
content,
"template",
Some(FrontmatterValue::String(template.to_string())),
)
.unwrap();
let matter = gray_matter::Matter::<gray_matter::engine::YAML>::new();
let parsed = matter.parse(&updated);
let data = parsed.data.unwrap();
if let gray_matter::Pod::Hash(map) = data {
let roundtripped = map.get("template").unwrap().as_string().unwrap();
assert!(roundtripped.contains("## Objective"));
assert!(roundtripped.contains("## Timeline"));
assert!(roundtripped.contains("Describe the goal."));
} else {
panic!("Expected hash");
}
}
}

View file

@ -0,0 +1,267 @@
use super::keys::{frontmatter_key_rule, frontmatter_keys_match, FrontmatterKey};
use super::yaml::{format_yaml_field, FrontmatterValue};
/// Check if a line continues the previous key's value (indented list item,
/// block scalar content, or blank line inside a block scalar).
fn is_value_continuation(line: FrontmatterLine<'_>) -> bool {
line.0.is_empty() || line.0.starts_with(" ") || line.0.starts_with('\t')
}
#[derive(Clone, Copy)]
enum KeyMatchMode {
Exact,
Canonical,
}
#[derive(Clone, Copy)]
struct DocumentText<'a>(&'a str);
#[derive(Clone, Copy)]
struct FrontmatterLine<'a>(&'a str);
#[derive(Clone, Copy)]
struct PropertyKey<'a>(&'a str);
#[derive(Clone, Copy)]
struct FrontmatterBlock<'a> {
body: &'a str,
rest: &'a str,
line_ending: &'static str,
}
impl<'a> PropertyKey<'a> {
fn as_str(self) -> &'a str {
self.0
}
fn matches(self, candidate: &str, mode: KeyMatchMode) -> bool {
match mode {
KeyMatchMode::Exact => candidate == self.as_str(),
KeyMatchMode::Canonical => frontmatter_keys_match(
FrontmatterKey::new(candidate),
FrontmatterKey::new(self.as_str()),
),
}
}
}
impl<'a> FrontmatterLine<'a> {
fn key(self) -> Option<&'a str> {
let trimmed = self.0.trim_start();
if let Some(raw) = trimmed.strip_prefix('"') {
return quoted_yaml_key(raw, '"');
}
if let Some(raw) = trimmed.strip_prefix('\'') {
return quoted_yaml_key(raw, '\'');
}
trimmed
.split_once(':')
.map(|(key, _)| key.trim())
.filter(|key| !key.is_empty())
}
}
fn quoted_yaml_key(raw: &str, quote: char) -> Option<&str> {
let (key, rest) = raw.split_once(quote)?;
rest.trim_start().starts_with(':').then_some(key)
}
fn frontmatter_open(content: &str) -> Option<(&str, &'static str)> {
content
.strip_prefix("---\n")
.map(|after| (after, "\n"))
.or_else(|| content.strip_prefix("---\r\n").map(|after| (after, "\r\n")))
}
fn close_marker(line_ending: &str) -> String {
format!("{line_ending}---")
}
fn split_frontmatter_block(content: &str) -> Result<Option<FrontmatterBlock<'_>>, String> {
let Some((after_open, line_ending)) = frontmatter_open(content) else {
return Ok(None);
};
if let Some(rest) = after_open.strip_prefix("---") {
return Ok(Some(FrontmatterBlock {
body: "",
rest,
line_ending,
}));
}
let marker = close_marker(line_ending);
let close_start = after_open
.find(&marker)
.ok_or_else(|| "Malformed frontmatter: no closing ---".to_string())?;
let rest_start = close_start + marker.len();
Ok(Some(FrontmatterBlock {
body: &after_open[..close_start],
rest: &after_open[rest_start..],
line_ending,
}))
}
#[derive(Clone, Copy)]
struct FieldUpdate<'a> {
key: PropertyKey<'a>,
value: Option<&'a FrontmatterValue>,
match_mode: KeyMatchMode,
}
impl<'a> FieldUpdate<'a> {
fn matches_line(self, line: FrontmatterLine<'_>) -> bool {
line.key()
.is_some_and(|candidate| self.key.matches(candidate, self.match_mode))
}
fn prepend_to(self, content: DocumentText<'_>) -> String {
let field_lines =
format_yaml_field(self.key.as_str(), self.value.expect("value must exist"));
format!("---\n{}\n---\n{}", field_lines.join("\n"), content.0)
}
fn apply_to_lines(self, lines: &[FrontmatterLine<'_>]) -> Vec<String> {
let mut new_lines: Vec<String> = Vec::new();
let mut found_key = false;
let mut i = 0;
while i < lines.len() {
if !self.matches_line(lines[i]) {
new_lines.push(lines[i].0.to_string());
i += 1;
continue;
}
found_key = true;
i += 1;
while i < lines.len() && is_value_continuation(lines[i]) {
i += 1;
}
if let Some(v) = self.value {
new_lines.extend(format_yaml_field(self.key.as_str(), v));
}
}
if let (false, Some(v)) = (found_key, self.value) {
new_lines.extend(format_yaml_field(self.key.as_str(), v));
}
new_lines
}
fn apply_to_content(self, content: DocumentText<'_>) -> Result<String, String> {
let Some(block) = split_frontmatter_block(content.0)? else {
return match self.value {
Some(_) => Ok(self.prepend_to(content)),
None => Ok(content.0.to_string()),
};
};
let lines: Vec<FrontmatterLine<'_>> = block.body.lines().map(FrontmatterLine).collect();
let new_fm = self.apply_to_lines(&lines).join(block.line_ending);
Ok(format!(
"---{}{}{}---{}",
block.line_ending, new_fm, block.line_ending, block.rest
))
}
}
/// Internal function to update frontmatter content
pub fn update_frontmatter_content(
content: &str,
key: &str,
value: Option<FrontmatterValue>,
) -> Result<String, String> {
let update = FieldUpdate {
key: PropertyKey(key),
value: value.as_ref(),
match_mode: KeyMatchMode::Exact,
};
let Some(rule) = frontmatter_key_rule(FrontmatterKey::new(update.key.as_str()))
.filter(|rule| rule.canonicalizes_on_write())
else {
return update.apply_to_content(DocumentText(content));
};
let updated = FieldUpdate {
key: PropertyKey(rule.write_key()),
value: None,
match_mode: KeyMatchMode::Canonical,
}
.apply_to_content(DocumentText(content))?;
FieldUpdate {
key: PropertyKey(rule.write_key()),
value: update.value,
match_mode: KeyMatchMode::Exact,
}
.apply_to_content(DocumentText(&updated))
}
#[cfg(test)]
mod tests {
use super::*;
fn bool_value(value: bool) -> FrontmatterValue {
FrontmatterValue::Bool(value)
}
fn string_value(value: &str) -> FrontmatterValue {
FrontmatterValue::String(value.to_string())
}
fn frontmatter_delimiter_lines(content: &str) -> usize {
content.lines().filter(|line| *line == "---").count()
}
#[test]
fn updates_existing_crlf_frontmatter_without_creating_a_second_block() {
let content = concat!(
"---\r\n",
"type: Note\r\n",
"related_to:\r\n",
" - \"[[tolaria]]\"\r\n",
"---\r\n",
"# Properties Panel\r\n",
);
let updated = update_frontmatter_content(content, "_organized", Some(bool_value(true)))
.expect("frontmatter update should succeed");
assert_eq!(frontmatter_delimiter_lines(&updated), 2);
assert_eq!(
updated,
concat!(
"---\r\n",
"type: Note\r\n",
"related_to:\r\n",
" - \"[[tolaria]]\"\r\n",
"_organized: true\r\n",
"---\r\n",
"# Properties Panel\r\n",
)
);
}
#[test]
fn repeated_crlf_updates_stay_in_the_original_frontmatter_block() {
let content = concat!(
"---\r\n",
"type: Note\r\n",
"related_to: \"[[tolaria]]\"\r\n",
"---\r\n",
"# Properties Panel\r\n",
);
let widened = update_frontmatter_content(content, "_width", Some(string_value("wide")))
.expect("width update should succeed");
let organized = update_frontmatter_content(&widened, "_organized", Some(bool_value(true)))
.expect("organized update should succeed");
assert_eq!(frontmatter_delimiter_lines(&organized), 2);
assert!(organized.contains("type: Note\r\n"));
assert!(organized.contains("_width: wide\r\n"));
assert!(organized.contains("_organized: true\r\n"));
}
}

View file

@ -0,0 +1,237 @@
use super::{update_frontmatter_content, FrontmatterValue};
struct UpdateCase<'a> {
content: &'a str,
key: &'a str,
value: Option<FrontmatterValue>,
expected_present: &'a [&'a str],
expected_absent: &'a [&'a str],
}
fn assert_updated_content(case: UpdateCase<'_>) {
let updated = update_frontmatter_content(case.content, case.key, case.value).unwrap();
for expected in case.expected_present {
assert!(
updated.contains(expected),
"missing expected snippet: {expected}"
);
}
for unexpected in case.expected_absent {
assert!(
!updated.contains(unexpected),
"found unexpected snippet: {unexpected}"
);
}
}
#[test]
fn test_update_frontmatter_replaces_or_adds_scalar_fields() {
let cases = [
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "Status",
value: Some(FrontmatterValue::String("Active".to_string())),
expected_present: &["Status: Active"],
expected_absent: &["Status: Draft"],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "Owner",
value: Some(FrontmatterValue::String("Luca".to_string())),
expected_present: &["Owner: Luca", "Status: Draft"],
expected_absent: &[],
},
UpdateCase {
content: "---\n\"Is A\": Note\n---\n# Test\n",
key: "Is A",
value: Some(FrontmatterValue::String("Project".to_string())),
expected_present: &["type: Project"],
expected_absent: &["\"Is A\": Note", "\"Is A\": Project"],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "Reviewed",
value: Some(FrontmatterValue::Bool(true)),
expected_present: &["Reviewed: true"],
expected_absent: &[],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "Priority",
value: Some(FrontmatterValue::Number(5.0)),
expected_present: &["Priority: 5"],
expected_absent: &[],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "Score",
value: Some(FrontmatterValue::Number(9.5)),
expected_present: &["Score: 9.5"],
expected_absent: &[],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "ClearMe",
value: Some(FrontmatterValue::Null),
expected_present: &["ClearMe: null"],
expected_absent: &[],
},
];
for case in cases {
assert_updated_content(case);
}
}
#[test]
fn test_update_frontmatter_list_and_delete_paths() {
let list_cases = [
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "aliases",
value: Some(FrontmatterValue::List(vec![
"Alias1".to_string(),
"Alias2".to_string(),
])),
expected_present: &["aliases:", " - \"Alias1\"", " - \"Alias2\""],
expected_absent: &[],
},
UpdateCase {
content: "---\naliases:\n - Old1\n - Old2\nStatus: Draft\n---\n# Test\n",
key: "aliases",
value: Some(FrontmatterValue::List(vec!["New1".to_string()])),
expected_present: &[" - \"New1\"", "Status: Draft"],
expected_absent: &["Old1", "Old2"],
},
UpdateCase {
content: "---\naliases:\n - Alias1\n - Alias2\nStatus: Draft\n---\n# Test\n",
key: "aliases",
value: None,
expected_present: &["Status: Draft"],
expected_absent: &["aliases", "Alias1"],
},
UpdateCase {
content: "---\nStatus: Draft\nOwner: Luca\n---\n# Test\n",
key: "Owner",
value: None,
expected_present: &["Status: Draft"],
expected_absent: &["Owner"],
},
UpdateCase {
content: "---\nStatus: Draft\n---\n# Test\n",
key: "tags",
value: Some(FrontmatterValue::List(vec![])),
expected_present: &["tags: []"],
expected_absent: &[],
},
];
for case in list_cases {
assert_updated_content(case);
}
}
#[test]
fn test_update_frontmatter_handles_missing_or_malformed_frontmatter() {
let inserted = update_frontmatter_content(
"# Test\n\nSome content here.",
"Status",
Some(FrontmatterValue::String("Draft".to_string())),
)
.unwrap();
assert!(inserted.starts_with("---\n"));
assert!(inserted.contains("Status: Draft"));
assert!(inserted.contains("# Test"));
let malformed = update_frontmatter_content(
"---\nStatus: Draft\nNo closing fence here",
"Status",
Some(FrontmatterValue::String("Active".to_string())),
);
assert!(malformed.is_err());
assert!(malformed.unwrap_err().contains("Malformed frontmatter"));
let unchanged =
update_frontmatter_content("---\nStatus: Draft\n---\n# Test\n", "Missing", None).unwrap();
assert_eq!(unchanged, "---\nStatus: Draft\n---\n# Test\n");
let no_frontmatter =
update_frontmatter_content("# Test\n\nSome content.", "Missing", None).unwrap();
assert_eq!(no_frontmatter, "# Test\n\nSome content.");
}
#[test]
fn test_update_frontmatter_canonicalizes_system_metadata_keys() {
let cases = [
UpdateCase {
content: "---\narchived: false\n---\n# Test\n",
key: "_archived",
value: Some(FrontmatterValue::Bool(true)),
expected_present: &["_archived: true"],
expected_absent: &["archived: false"],
},
UpdateCase {
content: "---\nicon: rocket\n---\n# Test\n",
key: "icon",
value: Some(FrontmatterValue::String("star".to_string())),
expected_present: &["_icon: star"],
expected_absent: &["\nicon:", "rocket"],
},
UpdateCase {
content: "---\nsidebar label: Projects\nsidebar_label: Legacy\n---\n# Test\n",
key: "_sidebar_label",
value: Some(FrontmatterValue::String("Programs".to_string())),
expected_present: &["_sidebar_label: Programs"],
expected_absent: &["sidebar label: Projects", "sidebar_label: Legacy"],
},
UpdateCase {
content: "---\nsort: modified:desc\n_sort: title:asc\n---\n# Test\n",
key: "_sort",
value: None,
expected_present: &["# Test"],
expected_absent: &["\nsort:", "\n_sort:"],
},
];
for case in cases {
assert_updated_content(case);
}
}
#[test]
fn test_update_frontmatter_canonicalizes_type_key_case() {
let cases = [
UpdateCase {
content: "---\nType: Note\n---\n# Test\n",
key: "type",
value: Some(FrontmatterValue::String("Project".to_string())),
expected_present: &["type: Project"],
expected_absent: &["Type: Note"],
},
UpdateCase {
content: "---\n\"Is A\": Note\nis_a: Topic\n---\n# Test\n",
key: "type",
value: Some(FrontmatterValue::String("Project".to_string())),
expected_present: &["type: Project"],
expected_absent: &["\"Is A\": Note", "is_a: Topic"],
},
UpdateCase {
content: "---\nTYPE: Note\n---\n# Test\n",
key: "Type",
value: Some(FrontmatterValue::String("Person".to_string())),
expected_present: &["type: Person"],
expected_absent: &["TYPE: Note"],
},
UpdateCase {
content: "---\nType: Note\nstatus: Active\n---\n# Test\n",
key: "type",
value: None,
expected_present: &["status: Active", "# Test"],
expected_absent: &["Type: Note", "\ntype:"],
},
];
for case in cases {
assert_updated_content(case);
}
}

View file

@ -0,0 +1,253 @@
use serde::{Deserialize, Serialize};
/// Value type for frontmatter updates
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum FrontmatterValue {
String(String),
Number(f64),
Bool(bool),
List(Vec<String>),
Null,
}
#[derive(Clone, Copy)]
struct YamlText<'a>(&'a str);
impl<'a> YamlText<'a> {
/// Characters that require a YAML string value to be quoted.
fn has_special_chars(self) -> bool {
self.0.contains(':') || self.0.contains('#')
}
/// Check if a string starts with a YAML collection indicator (array or map).
fn starts_as_collection(self) -> bool {
self.0.starts_with('[') || self.0.starts_with('{')
}
/// Check whether a YAML string value needs quoting to avoid ambiguity.
fn needs_quoting(self) -> bool {
self.has_special_chars()
|| self.starts_as_collection()
|| matches!(self.0, "true" | "false" | "null")
|| self.0.parse::<f64>().is_ok()
}
/// Quote a string value for YAML, escaping internal double quotes.
fn quoted(self) -> String {
format!("\"{}\"", self.0.replace('\"', "\\\""))
}
/// Format a single YAML list item as ` - "value"`.
fn as_list_item(self) -> String {
format!(" - {}", self.quoted())
}
/// Format a multi-line string as a YAML block scalar (`|`).
/// Each line is indented by 2 spaces; empty lines are preserved as blank.
fn as_block_scalar(self) -> String {
let indented = self
.0
.lines()
.map(|line| {
if line.is_empty() {
String::new()
} else {
format!(" {}", line)
}
})
.collect::<Vec<_>>()
.join("\n");
format!("|\n{}", indented)
}
/// Check whether a YAML key needs quoting (contains spaces, special chars, etc.).
fn needs_key_quoting(self) -> bool {
self.0
.chars()
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-')
}
}
/// Format a key for YAML output (quote if necessary)
pub fn format_yaml_key(key: &str) -> String {
let yaml_key = YamlText(key);
if yaml_key.needs_key_quoting() {
yaml_key.quoted()
} else {
key.to_string()
}
}
/// Format a number for YAML (integers without decimal, floats with).
fn format_yaml_number(n: f64) -> String {
if n.fract() == 0.0 {
format!("{}", n as i64)
} else {
format!("{}", n)
}
}
impl FrontmatterValue {
pub fn to_yaml_value(&self) -> String {
match self {
FrontmatterValue::String(s) => {
let yaml_text = YamlText(s);
if s.contains('\n') {
yaml_text.as_block_scalar()
} else if yaml_text.needs_quoting() {
yaml_text.quoted()
} else {
s.clone()
}
}
FrontmatterValue::Number(n) => format_yaml_number(*n),
FrontmatterValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
FrontmatterValue::List(items) if items.is_empty() => "[]".to_string(),
FrontmatterValue::List(items) => items
.iter()
.map(|item| YamlText(item).as_list_item())
.collect::<Vec<_>>()
.join("\n"),
FrontmatterValue::Null => "null".to_string(),
}
}
}
/// Format a key-value pair as one or more YAML lines.
pub fn format_yaml_field(key: &str, value: &FrontmatterValue) -> Vec<String> {
let yaml_key = format_yaml_key(key);
let yaml_value = value.to_yaml_value();
if yaml_value.starts_with("|\n") {
// Block scalar: key and indicator on the same line, content follows
vec![format!("{}: {}", yaml_key, yaml_value)]
} else if matches!(value, FrontmatterValue::List(items) if !items.is_empty()) {
vec![format!("{}:", yaml_key), yaml_value]
} else {
vec![format!("{}: {}", yaml_key, yaml_value)]
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_string_yaml_value(input: &str, expected: &str) {
let value = FrontmatterValue::String(input.to_string());
assert_eq!(value.to_yaml_value(), expected);
}
fn assert_field_lines(key: &str, value: FrontmatterValue, expected: &[&str]) {
let lines = format_yaml_field(key, &value);
let expected_lines = expected
.iter()
.map(|line| line.to_string())
.collect::<Vec<_>>();
assert_eq!(lines, expected_lines);
}
#[test]
fn test_to_yaml_value_string_needs_quoting_cases() {
for (input, expected) in [
("key: value", "\"key: value\""),
("has # comment", "\"has # comment\""),
("[array-like]", "\"[array-like]\""),
("{object-like}", "\"{object-like}\""),
("true", "\"true\""),
("false", "\"false\""),
("null", "\"null\""),
("42", "\"42\""),
("3.14", "\"3.14\""),
] {
assert_string_yaml_value(input, expected);
}
}
#[test]
fn test_to_yaml_value_string_plain() {
assert_string_yaml_value("Hello World", "Hello World");
}
#[test]
fn test_to_yaml_value_number_integer() {
let v = FrontmatterValue::Number(42.0);
assert_eq!(v.to_yaml_value(), "42");
}
#[test]
fn test_to_yaml_value_number_float() {
let v = FrontmatterValue::Number(3.125);
assert_eq!(v.to_yaml_value(), "3.125");
}
#[test]
fn test_to_yaml_value_null() {
assert_eq!(FrontmatterValue::Null.to_yaml_value(), "null");
}
#[test]
fn test_to_yaml_value_empty_list() {
let v = FrontmatterValue::List(vec![]);
assert_eq!(v.to_yaml_value(), "[]");
}
#[test]
fn test_to_yaml_value_list_with_colon() {
let v = FrontmatterValue::List(vec!["key: value".to_string()]);
assert_eq!(v.to_yaml_value(), " - \"key: value\"");
}
#[test]
fn test_to_yaml_value_multiline_uses_block_scalar() {
let v = FrontmatterValue::String("line 1\nline 2\nline 3".to_string());
let yaml = v.to_yaml_value();
assert!(yaml.starts_with("|\n"));
assert!(yaml.contains(" line 1"));
assert!(yaml.contains(" line 2"));
}
#[test]
fn test_format_yaml_key_simple() {
for (input, expected) in [("Status", "Status"), ("is_a", "is_a")] {
assert_eq!(format_yaml_key(input), expected);
}
}
#[test]
fn test_format_yaml_key_quotes_when_needed() {
for (input, expected) in [
("Is A", "\"Is A\""),
("Created at", "\"Created at\""),
("key:value", "\"key:value\""),
("has#tag", "\"has#tag\""),
("key.name", "\"key.name\""),
] {
assert_eq!(format_yaml_key(input), expected);
}
}
#[test]
fn test_format_yaml_field_block_scalar() {
let v = FrontmatterValue::String("## Objective\n\n## Timeline".to_string());
let lines = format_yaml_field("template", &v);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("template: |\n"));
assert!(lines[0].contains(" ## Objective"));
assert!(lines[0].contains(" ## Timeline"));
}
#[test]
fn test_format_yaml_field_list_layouts() {
assert_field_lines(
"_list_properties_display",
FrontmatterValue::List(vec!["Belongs to".to_string()]),
&["_list_properties_display:", " - \"Belongs to\""],
);
assert_field_lines("tags", FrontmatterValue::List(vec![]), &["tags: []"]);
assert_field_lines(
"tags",
FrontmatterValue::List(vec!["Alpha".to_string(), "Beta".to_string()]),
&["tags:", " - \"Alpha\"\n - \"Beta\""],
);
}
}