Skip to main content

cdx_core/presentation/
toc.rs

1//! Table of contents configuration.
2
3use serde::{Deserialize, Serialize};
4
5/// Table of contents configuration.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct TocConfig {
9    /// Custom title for the table of contents.
10    #[serde(default, skip_serializing_if = "Option::is_none")]
11    pub title: Option<String>,
12
13    /// Which heading levels to include (e.g., [1, 2, 3]).
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub levels: Option<Vec<u32>>,
16
17    /// Whether to show page numbers.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub page_numbers: Option<bool>,
20
21    /// Leader style between title and page number.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub leaders: Option<TocLeaders>,
24}
25
26impl TocConfig {
27    /// Create a new table of contents configuration.
28    #[must_use]
29    pub fn new() -> Self {
30        Self {
31            title: None,
32            levels: None,
33            page_numbers: None,
34            leaders: None,
35        }
36    }
37
38    /// Set the TOC title.
39    #[must_use]
40    pub fn with_title(mut self, title: impl Into<String>) -> Self {
41        self.title = Some(title.into());
42        self
43    }
44
45    /// Set which heading levels to include.
46    #[must_use]
47    pub fn with_levels(mut self, levels: Vec<u32>) -> Self {
48        self.levels = Some(levels);
49        self
50    }
51
52    /// Enable page numbers.
53    #[must_use]
54    pub const fn with_page_numbers(mut self) -> Self {
55        self.page_numbers = Some(true);
56        self
57    }
58
59    /// Set the leader style.
60    #[must_use]
61    pub const fn with_leaders(mut self, leaders: TocLeaders) -> Self {
62        self.leaders = Some(leaders);
63        self
64    }
65}
66
67impl Default for TocConfig {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73/// Leader style for table of contents entries.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "lowercase")]
76pub enum TocLeaders {
77    /// Dotted leaders (. . . . .).
78    Dots,
79    /// Dashed leaders (- - - - -).
80    Dashes,
81    /// No leaders.
82    #[serde(rename = "none")]
83    NoLeaders,
84    /// Solid line leaders.
85    Solid,
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_toc_config_serde() {
94        let config = TocConfig::new()
95            .with_title("Table of Contents")
96            .with_levels(vec![1, 2, 3])
97            .with_page_numbers()
98            .with_leaders(TocLeaders::Dots);
99
100        let json = serde_json::to_string_pretty(&config).unwrap();
101        assert!(json.contains("\"title\": \"Table of Contents\""));
102        assert!(json.contains("\"pageNumbers\": true"));
103        assert!(json.contains("\"leaders\": \"dots\""));
104
105        let parsed: TocConfig = serde_json::from_str(&json).unwrap();
106        assert_eq!(parsed, config);
107    }
108
109    #[test]
110    fn test_toc_leaders_serde() {
111        assert_eq!(
112            serde_json::to_string(&TocLeaders::Dots).unwrap(),
113            "\"dots\""
114        );
115        assert_eq!(
116            serde_json::to_string(&TocLeaders::Dashes).unwrap(),
117            "\"dashes\""
118        );
119        assert_eq!(
120            serde_json::to_string(&TocLeaders::NoLeaders).unwrap(),
121            "\"none\""
122        );
123        assert_eq!(
124            serde_json::to_string(&TocLeaders::Solid).unwrap(),
125            "\"solid\""
126        );
127    }
128
129    #[test]
130    fn test_toc_defaults() {
131        let json = "{}";
132        let config: TocConfig = serde_json::from_str(json).unwrap();
133        assert!(config.title.is_none());
134        assert!(config.levels.is_none());
135        assert!(config.page_numbers.is_none());
136        assert!(config.leaders.is_none());
137    }
138}