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 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 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
47fn process_chapter(chapter: &mut Chapter, operators: &Value, network: &str) {
49 let mut content = chapter.content.clone();
50
51 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 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 https_urls.sort_unstable();
73 http_urls.sort_unstable();
74 ip_urls.sort_unstable();
75
76 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 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 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}