use crate::phase::format::FormatMode;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum DocsMode {
#[default]
Off,
Markdown,
Text,
All,
}
impl DocsMode {
pub fn parse(value: &str) -> Option<Self> {
match value.to_ascii_lowercase().as_str() {
"off" => Some(Self::Off),
"markdown" => Some(Self::Markdown),
"text" => Some(Self::Text),
"all" => Some(Self::All),
_ => None,
}
}
pub fn include_markdown(self) -> bool {
matches!(self, Self::Markdown | Self::All)
}
pub fn include_text(self) -> bool {
matches!(self, Self::Text | Self::All)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhaseOptions {
pub root: PathBuf,
#[serde(default)]
pub focus_files: Vec<PathBuf>,
pub mode: FormatMode,
pub max_files: usize,
pub max_focus_files: usize,
pub top_n: usize,
pub max_output_chars: usize,
pub use_incremental_refresh: bool,
pub include_docs: bool,
pub docs_mode: DocsMode,
#[serde(default = "default_hotspot_keywords")]
pub hotspot_keywords: Vec<String>,
}
impl Default for PhaseOptions {
fn default() -> Self {
Self {
root: PathBuf::new(),
focus_files: Vec::new(),
mode: FormatMode::Balanced,
max_files: 2_000,
max_focus_files: 20,
top_n: 10,
max_output_chars: FormatMode::Balanced.default_max_chars(),
use_incremental_refresh: true,
include_docs: false,
docs_mode: DocsMode::Off,
hotspot_keywords: default_hotspot_keywords(),
}
}
}
fn default_hotspot_keywords() -> Vec<String> {
vec![
"auth".to_string(),
"critical".to_string(),
"error".to_string(),
]
}
impl PhaseOptions {
pub fn normalized(mut self) -> Self {
if !self.include_docs {
self.docs_mode = DocsMode::Off;
}
if self.max_output_chars == 0 {
self.max_output_chars = self.mode.default_max_chars();
}
if self.hotspot_keywords.is_empty() {
self.hotspot_keywords = PhaseOptions::default().hotspot_keywords;
}
if !self.focus_files.is_empty() {
self.focus_files.sort();
self.focus_files.dedup();
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn docs_mode_parse_and_flags_work() {
assert_eq!(DocsMode::parse("markdown"), Some(DocsMode::Markdown));
assert_eq!(DocsMode::parse("text"), Some(DocsMode::Text));
assert_eq!(DocsMode::parse("all"), Some(DocsMode::All));
assert_eq!(DocsMode::parse("invalid"), None);
assert!(DocsMode::Markdown.include_markdown());
assert!(!DocsMode::Markdown.include_text());
assert!(DocsMode::All.include_markdown());
assert!(DocsMode::All.include_text());
}
#[test]
fn normalized_disables_docs_when_not_opted_in() {
let normalized = PhaseOptions {
include_docs: false,
docs_mode: DocsMode::All,
max_output_chars: 0,
hotspot_keywords: Vec::new(),
..PhaseOptions::default()
}
.normalized();
assert_eq!(normalized.docs_mode, DocsMode::Off);
assert_eq!(
normalized.max_output_chars,
normalized.mode.default_max_chars()
);
assert!(!normalized.hotspot_keywords.is_empty());
}
#[test]
fn default_requires_explicit_root_assignment() {
assert!(PhaseOptions::default().root.as_os_str().is_empty());
}
}