Files
huskies/server/src/io/story_metadata/parser.rs
T

94 lines
3.0 KiB
Rust
Raw Normal View History

2026-05-08 14:24:20 +00:00
//! Pure-content helpers and CRDT-backed metadata lookups.
//!
//! Story 865 stripped YAML front matter from stored content and the codebase
//! at large; the only remaining functions here read the CRDT or operate on
//! the markdown body directly.
use super::types::QaMode;
2026-04-28 16:25:47 +00:00
/// Parse unchecked todo items (`- [ ] ...`) from a markdown string.
pub fn parse_unchecked_todos(contents: &str) -> Vec<String> {
contents
.lines()
.filter_map(|line| {
let trimmed = line.trim();
trimmed.strip_prefix("- [ ] ").map(|text| text.to_string())
})
.collect()
}
2026-05-08 14:24:20 +00:00
/// Resolve the effective QA mode for a story by ID via the CRDT.
2026-04-28 16:25:47 +00:00
///
2026-05-08 14:24:20 +00:00
/// Returns `default` when the story has no entry or its `qa_mode` register is
/// unset. Spikes are **not** handled here — callers override to `Human` for
/// spikes themselves.
pub fn resolve_qa_mode(story_id: &str, default: QaMode) -> QaMode {
crate::crdt_state::read_item(story_id)
.and_then(|view| view.qa_mode)
.as_deref()
.and_then(QaMode::from_str)
.unwrap_or(default)
2026-04-28 16:25:47 +00:00
}
2026-05-08 14:24:20 +00:00
/// Resolve the effective QA mode by parsing legacy YAML front matter from a
/// markdown body. Used during one-time fallbacks when the CRDT register isn't
/// set; new code should always read `qa_mode` from the CRDT.
pub fn resolve_qa_mode_from_content(_story_id: &str, content: &str, default: QaMode) -> QaMode {
crate::db::yaml_legacy::parse_front_matter(content)
.ok()
.and_then(|m| m.qa)
.unwrap_or(default)
2026-04-28 16:25:47 +00:00
}
2026-04-29 22:12:23 +00:00
/// Return `true` if the story is in the `Frozen` pipeline stage.
2026-04-28 16:25:47 +00:00
///
2026-05-08 14:24:20 +00:00
/// Checks the typed CRDT stage via `read_typed`. Used by the pipeline advance
2026-04-29 22:12:23 +00:00
/// code to suppress stage transitions for frozen stories.
2026-04-28 16:25:47 +00:00
pub fn is_story_frozen_in_store(story_id: &str) -> bool {
2026-04-29 22:12:23 +00:00
crate::pipeline_state::read_typed(story_id)
2026-04-28 16:25:47 +00:00
.ok()
2026-04-29 22:12:23 +00:00
.flatten()
.map(|item| item.stage.is_frozen())
2026-04-28 16:25:47 +00:00
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_unchecked_todos_mixed() {
let input = "## AC\n- [ ] First thing\n- [x] Done thing\n- [ ] Second thing\n";
assert_eq!(
parse_unchecked_todos(input),
vec!["First thing", "Second thing"]
);
}
#[test]
fn parse_unchecked_todos_all_checked() {
let input = "- [x] Done\n- [x] Also done\n";
assert!(parse_unchecked_todos(input).is_empty());
}
#[test]
fn parse_unchecked_todos_no_checkboxes() {
let input = "# Story\nSome text\n- A bullet\n";
assert!(parse_unchecked_todos(input).is_empty());
}
#[test]
fn parse_unchecked_todos_leading_whitespace() {
let input = " - [ ] Indented item\n";
assert_eq!(parse_unchecked_todos(input), vec!["Indented item"]);
}
#[test]
2026-05-08 14:24:20 +00:00
fn resolve_qa_mode_falls_back_to_default_when_crdt_empty() {
crate::crdt_state::init_for_test();
assert_eq!(
resolve_qa_mode("9999_no_such_story", QaMode::Server),
QaMode::Server
);
2026-04-28 16:25:47 +00:00
}
}