mdbook_templates/
lib.rs

1use mdbook::book::{Book, Chapter};
2use mdbook::errors::Error;
3use mdbook::preprocess::{Preprocessor, PreprocessorContext};
4use serde_json::Value;
5use std::fs;
6
7pub struct TemplatesPreprocessor;
8
9impl TemplatesPreprocessor {
10    pub fn new() -> TemplatesPreprocessor {
11        TemplatesPreprocessor
12    }
13
14    pub fn supports_renderer(&self, renderer: &str) -> bool {
15        renderer != "not-supported"
16    }
17}
18
19impl Preprocessor for TemplatesPreprocessor {
20    fn name(&self) -> &str {
21        "operators"
22    }
23
24    fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
25        // Get operators path from config, fallback to default if not specified
26        let operators_path = ctx.config
27            .get("preprocessor.templates.path")
28            .and_then(|v| v.as_str())
29            .map(|p| ctx.root.join(p))
30            .unwrap();
31
32        let operators_content = fs::read_to_string(operators_path)?;
33        let operators: Value = serde_json::from_str(&operators_content)?;
34
35        // Process each chapter
36        book.for_each_mut(|item| {
37            if let mdbook::book::BookItem::Chapter(chapter) = item {
38                process_chapter(chapter, &operators, "mainnet");
39                process_chapter(chapter, &operators, "testnet");
40            }
41        });
42
43        Ok(book)
44    }
45}
46
47//TODO: Make this a method mustache generic template
48fn process_chapter(chapter: &mut Chapter, operators: &Value, network: &str) {
49    let mut content = chapter.content.clone();
50
51    // Helper function to sort and format URLs
52    let format_sorted_urls = |urls: &serde_json::Map<String, Value>| -> String {
53        let mut https_urls: Vec<String> = Vec::new();
54        let mut http_urls: Vec<String> = Vec::new();
55        let mut ip_urls: Vec<String> = Vec::new();
56
57        // Separate URLs into three categories
58        for key in urls.keys() {
59            let url = key.to_string();
60            if url.starts_with("https://") {
61                https_urls.push(url);
62            } else if url.starts_with("http://") {
63                if url[7..].chars().next().unwrap_or('a').is_ascii_digit() {
64                    ip_urls.push(url);
65                } else {
66                    http_urls.push(url);
67                }
68            }
69        }
70
71        // Sort all vectors
72        https_urls.sort_unstable();
73        http_urls.sort_unstable();
74        ip_urls.sort_unstable();
75
76        // Create the combined sorted list: HTTPS first, then HTTP domains, then IP addresses
77        let mut list = String::new();
78        for url in https_urls {
79            list.push_str(&format!("- `{}`\n", url));
80        }
81        for url in http_urls {
82            list.push_str(&format!("- `{}`\n", url));
83        }
84        for url in ip_urls {
85            list.push_str(&format!("- `{}`\n", url));
86        }
87        list
88    };
89
90    // Process aggregators
91    if let Some(aggregators) = operators.get(network)
92        .and_then(|net| net.get("aggregators"))
93        .and_then(|agg_obj| agg_obj.as_object()) {
94        let agg_list = format_sorted_urls(aggregators);
95        if let Some((start, end)) = find_section(&content, &format!("{{ #{}.aggregators }}", network), &format!("{{ /{}.aggregators }}", network)) {
96            let before = &content[..start];
97            let after = &content[end..];
98            content = format!("{}{}{}", before, agg_list, after);
99        }
100    }
101
102    // Process publishers
103    if let Some(publishers) = operators.get(network)
104        .and_then(|net| net.get("publishers"))
105        .and_then(|pub_obj| pub_obj.as_object()) {
106        let pub_list = format_sorted_urls(publishers);
107        if let Some((start, end)) = find_section(&content, &format!("{{ #{}.publishers }}", network), &format!("{{ /{}.publishers }}", network)) {
108            let before = &content[..start];
109            let after = &content[end..];
110            content = format!("{}{}{}", before, pub_list, after);
111        }
112    }
113
114    chapter.content = content;
115}
116
117fn find_section<'a>(content: &'a str, start_marker: &str, end_marker: &str) -> Option<(usize, usize)> {
118    let start = content.find(start_marker)?;
119    let end = content[start..].find(end_marker)?;
120    Some((start - 1, start + end + end_marker.len() + 2))
121}