mdbook_embedify/assets/scripts/
include.rs1use crate::detect_lang;
2use crate::parser;
3
4use mdbook_preprocessor::PreprocessorContext;
5use std::fs;
6
7fn wrap_content_in_code_block(content: String, language: String, lang_detected: String) -> String {
8 let backticks = if lang_detected == "markdown" {
9 let max_backticks = content
11 .lines()
12 .filter_map(|line| {
13 let trimmed = line.trim();
14 if trimmed.starts_with("```") {
15 Some(trimmed.chars().take_while(|&c| c == '`').count())
16 } else {
17 None
18 }
19 })
20 .max()
21 .unwrap_or(2) + 1; "`".repeat(max_backticks)
25 } else {
26 "```".to_string()
27 };
28
29 format!("{}{}\n{}\n{}", backticks, language, content, backticks)
30}
31
32fn parse_include_range(input: String, max_line: usize) -> (usize, usize) {
33 let trimmed = input.trim();
34
35 if trimmed.is_empty() || trimmed == "-" {
36 return (1, max_line);
37 }
38
39 let parts: Vec<&str> = trimmed.split('-').collect();
41
42 if parts.len() != 2 {
43 return (1, max_line);
44 }
45
46 let start = parts[0].trim();
47 let end = parts[1].trim();
48
49 let start_line = if start.is_empty() {
50 1
51 } else {
52 match start.parse::<i32>() {
53 Ok(n) if n >= 1 && n as usize <= max_line => n as usize,
54 _ => 1, }
56 };
57
58 let end_line = if end.is_empty() {
59 max_line
60 } else {
61 match end.parse::<i32>() {
62 Ok(n) if n >= start_line as i32 && n as usize <= max_line => n as usize,
63 _ => max_line, }
65 };
66
67 (start_line, end_line)
68}
69
70pub fn include_script(
71 ctx: &PreprocessorContext,
72 options: Vec<parser::EmbedAppOption>,
73) -> Result<String, String> {
74 let file_path_option = parser::get_option("file", options.clone());
76 if file_path_option.is_none() {
77 return Err("Option file is required".to_string());
78 }
79
80 let file_path = ctx
81 .root
82 .join(file_path_option.unwrap().value)
83 .to_string_lossy()
84 .to_string();
85
86 if !std::path::Path::new(&file_path).exists() {
88 return Err(format!("Cannot find file {}", file_path));
89 }
90
91 let content = fs::read_to_string(&file_path).unwrap_or_else(|_| String::new());
92
93 let range_option = parser::get_option("range", options.clone());
95 let max_line = content.lines().count();
96 let range = match range_option {
97 Some(option) => parse_include_range(option.value.clone(), max_line),
98 _ => (1, max_line), };
100
101 let start = range.0 - 1; let end = range.1; let lines: Vec<&str> = content.lines().collect();
105 let content = lines[start..end].join("\n");
106
107 let lang_option = parser::get_option("lang", options.clone());
109 let lang_detected = detect_lang::detect_lang(file_path.clone(), Some(&ctx.config));
110
111 let language = match lang_option {
112 Some(option) => option.value,
113 _ => lang_detected.clone(),
114 };
115
116 Ok(wrap_content_in_code_block(content, language, lang_detected))
117}