Skip to main content

batuta/content/
types.rs

1//! Content type definitions
2//!
3//! Content type taxonomy from spec section 2.1.
4
5use super::ContentError;
6use serde::{Deserialize, Serialize};
7use std::ops::Range;
8use std::str::FromStr;
9
10/// Content type taxonomy from spec section 2.1
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum ContentType {
13    /// High-Level Outline (HLO) - Course/book structure planning
14    HighLevelOutline,
15    /// Detailed Outline (DLO) - Section-level content planning
16    DetailedOutline,
17    /// Book Chapter (BCH) - Technical documentation (mdBook)
18    BookChapter,
19    /// Blog Post (BLP) - Technical articles
20    BlogPost,
21    /// Presentar Demo (PDM) - Interactive WASM demos
22    PresentarDemo,
23}
24
25/// Course level configuration for detailed outlines
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
27pub enum CourseLevel {
28    /// Short course: 1 week, 2 modules, 3 videos each
29    Short,
30    /// Standard course: 3 weeks, 3 modules, 5 videos each (default)
31    #[default]
32    Standard,
33    /// Extended course: 6 weeks, 6 modules, 5 videos each
34    Extended,
35    /// Custom configuration
36    Custom { weeks: u8, modules: u8, videos_per_module: u8 },
37}
38
39impl CourseLevel {
40    /// Get duration in weeks
41    pub fn weeks(&self) -> u8 {
42        match self {
43            CourseLevel::Short => 1,
44            CourseLevel::Standard => 3,
45            CourseLevel::Extended => 6,
46            CourseLevel::Custom { weeks, .. } => *weeks,
47        }
48    }
49
50    /// Get number of modules
51    pub fn modules(&self) -> u8 {
52        match self {
53            CourseLevel::Short => 2,
54            CourseLevel::Standard => 3,
55            CourseLevel::Extended => 6,
56            CourseLevel::Custom { modules, .. } => *modules,
57        }
58    }
59
60    /// Get videos per module
61    pub fn videos_per_module(&self) -> u8 {
62        match self {
63            CourseLevel::Short => 3,
64            CourseLevel::Standard => 5,
65            CourseLevel::Extended => 5,
66            CourseLevel::Custom { videos_per_module, .. } => *videos_per_module,
67        }
68    }
69}
70
71impl FromStr for CourseLevel {
72    type Err = ContentError;
73
74    fn from_str(s: &str) -> Result<Self, Self::Err> {
75        match s.to_lowercase().as_str() {
76            "short" | "s" | "1" => Ok(CourseLevel::Short),
77            "standard" | "std" | "3" => Ok(CourseLevel::Standard),
78            "extended" | "ext" | "6" => Ok(CourseLevel::Extended),
79            _ => Err(ContentError::InvalidContentType(format!(
80                "Invalid course level: {}. Use: short, standard, extended",
81                s
82            ))),
83        }
84    }
85}
86
87impl ContentType {
88    /// Get all metadata for this content type as a single lookup
89    fn metadata(&self) -> (&'static str, &'static str, &'static str, Range<usize>) {
90        match self {
91            Self::HighLevelOutline => ("HLO", "High-Level Outline", "YAML/Markdown", 50..200),
92            Self::DetailedOutline => ("DLO", "Detailed Outline", "YAML/Markdown", 200..1000),
93            Self::BookChapter => ("BCH", "Book Chapter", "Markdown (mdBook)", 2000..8000),
94            Self::BlogPost => ("BLP", "Blog Post", "Markdown + TOML", 500..3000),
95            Self::PresentarDemo => ("PDM", "Presentar Demo", "HTML + YAML", 0..0),
96        }
97    }
98
99    /// Get the short code for this content type
100    pub fn code(&self) -> &'static str {
101        self.metadata().0
102    }
103
104    /// Get the display name
105    pub fn name(&self) -> &'static str {
106        self.metadata().1
107    }
108
109    /// Get the output format
110    pub fn output_format(&self) -> &'static str {
111        self.metadata().2
112    }
113
114    /// Get the target length range (in words for text, lines for outlines)
115    pub fn target_length(&self) -> Range<usize> {
116        self.metadata().3
117    }
118
119    /// Get all content types
120    pub fn all() -> Vec<ContentType> {
121        vec![
122            ContentType::HighLevelOutline,
123            ContentType::DetailedOutline,
124            ContentType::BookChapter,
125            ContentType::BlogPost,
126            ContentType::PresentarDemo,
127        ]
128    }
129}
130
131impl FromStr for ContentType {
132    type Err = ContentError;
133
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        match s.to_lowercase().as_str() {
136            "hlo" | "high-level-outline" | "outline" => Ok(ContentType::HighLevelOutline),
137            "dlo" | "detailed-outline" | "detailed" => Ok(ContentType::DetailedOutline),
138            "bch" | "book-chapter" | "chapter" => Ok(ContentType::BookChapter),
139            "blp" | "blog-post" | "blog" => Ok(ContentType::BlogPost),
140            "pdm" | "presentar-demo" | "demo" => Ok(ContentType::PresentarDemo),
141            _ => Err(ContentError::InvalidContentType(s.to_string())),
142        }
143    }
144}