|
| 1 | +//! Mission log command |
| 2 | +
|
| 3 | +use std::fs; |
| 4 | +use std::path::Path; |
| 5 | + |
| 6 | +use anyhow::Result; |
| 7 | + |
| 8 | +use keel::domain::model::Board; |
| 9 | +use keel::infrastructure::loader::load_board; |
| 10 | +use keel::read_model::show_selector::{ShowEntityKind, resolve_show_selector}; |
| 11 | + |
| 12 | +/// Show the mission log for a mission with an explicit board directory. |
| 13 | +pub fn run_with_dir(board_dir: &Path, id: &str) -> Result<()> { |
| 14 | + let content = read_log_with_dir(board_dir, id)?; |
| 15 | + print!("{content}"); |
| 16 | + if !content.ends_with('\n') { |
| 17 | + println!(); |
| 18 | + } |
| 19 | + Ok(()) |
| 20 | +} |
| 21 | + |
| 22 | +/// Read the raw mission log content for a mission. |
| 23 | +pub fn read_log_with_dir(board_dir: &Path, id: &str) -> Result<String> { |
| 24 | + let board = load_board(board_dir)?; |
| 25 | + let resolved_id = resolve_mission_id_with_board(board_dir, &board, id)?; |
| 26 | + let mission = board.require_mission(&resolved_id)?; |
| 27 | + let log_path = mission.path.parent().unwrap().join("LOG.md"); |
| 28 | + Ok(fs::read_to_string(log_path)?) |
| 29 | +} |
| 30 | + |
| 31 | +/// Resolve a mission ID or HEAD selector for mission-log operations. |
| 32 | +pub fn resolve_mission_id(board_dir: &Path, id: &str) -> Result<String> { |
| 33 | + let board = load_board(board_dir)?; |
| 34 | + resolve_mission_id_with_board(board_dir, &board, id) |
| 35 | +} |
| 36 | + |
| 37 | +fn resolve_mission_id_with_board(board_dir: &Path, board: &Board, id: &str) -> Result<String> { |
| 38 | + Ok(resolve_show_selector( |
| 39 | + board_dir, |
| 40 | + board, |
| 41 | + ShowEntityKind::Mission, |
| 42 | + id, |
| 43 | + )?) |
| 44 | +} |
| 45 | + |
| 46 | +#[cfg(test)] |
| 47 | +mod tests { |
| 48 | + use super::*; |
| 49 | + use keel::application::mission_lifecycle::MissionLifecycleService; |
| 50 | + use keel::test_helpers::{TestBoardBuilder, TestMission}; |
| 51 | + |
| 52 | + #[test] |
| 53 | + fn read_log_with_dir_returns_full_log_contents() { |
| 54 | + let temp = TestBoardBuilder::new() |
| 55 | + .mission(TestMission::new("M1").title("Mission One")) |
| 56 | + .build(); |
| 57 | + |
| 58 | + MissionLifecycleService::log(temp.path(), "M1", "First entry").unwrap(); |
| 59 | + MissionLifecycleService::log(temp.path(), "M1", "Second entry").unwrap(); |
| 60 | + |
| 61 | + let log = read_log_with_dir(temp.path(), "M1").unwrap(); |
| 62 | + |
| 63 | + assert!(log.contains("# Mission One - Decision Log")); |
| 64 | + assert!(log.contains("First entry")); |
| 65 | + assert!(log.contains("Second entry")); |
| 66 | + } |
| 67 | + |
| 68 | + #[test] |
| 69 | + fn resolve_mission_id_accepts_head_selectors() { |
| 70 | + let temp = TestBoardBuilder::new() |
| 71 | + .mission(TestMission::new("M2").title("Mission Two")) |
| 72 | + .mission(TestMission::new("M1").title("Mission One")) |
| 73 | + .build(); |
| 74 | + |
| 75 | + assert_eq!(resolve_mission_id(temp.path(), "HEAD").unwrap(), "M1"); |
| 76 | + assert_eq!(resolve_mission_id(temp.path(), "HEAD~").unwrap(), "M2"); |
| 77 | + } |
| 78 | +} |
0 commit comments