1pub mod entries;
14pub mod entry;
15use serde::Serialize;
16use serde_json::to_string_pretty;
17
18use crate::docs::entry::{
19 ConceptCategory, ConceptEntry, DescriptionEntry, DescriptionKind, FnEntry, StdEntry,
20};
21
22pub fn find_fn_doc(
29 module: Option<&str>,
30 fn_name: &str,
31) -> Option<(&'static StdEntry, &'static FnEntry)> {
32 for std_entry in entries::stdlib_entries() {
33 if let Some(m) = module
34 && std_entry.name != m
35 {
36 continue;
37 }
38 for func in std_entry.functions {
39 let bare = func.signature.split('(').next().unwrap_or(func.signature);
40 if bare == fn_name {
41 return Some((std_entry, func));
42 }
43 }
44 }
45 None
46}
47
48impl ConceptCategory {
49 fn label(&self) -> &'static str {
51 match self {
52 ConceptCategory::Syntax => "syntax",
53 ConceptCategory::Types => "types",
54 ConceptCategory::ControlFlow => "control flow",
55 ConceptCategory::Functions => "functions",
56 ConceptCategory::Modules => "modules",
57 ConceptCategory::Tooling => "tooling",
58 ConceptCategory::ErrorHandling => "error handling",
59 }
60 }
61}
62
63impl DescriptionKind {
64 fn label(&self) -> Option<&'static str> {
67 match self {
68 DescriptionKind::Explanation => None,
69 DescriptionKind::Syntax => Some("Syntax"),
70 DescriptionKind::Pitfall => Some("⚠ Pitfall"),
71 DescriptionKind::Note => Some("Note"),
72 }
73 }
74}
75
76fn render_related(label: &str, names: &[&str], prefix: &str) -> String {
79 if names.is_empty() {
80 return String::new();
81 }
82 let links = names
83 .iter()
84 .map(|n| format!("`{}{}`", prefix, n))
85 .collect::<Vec<_>>()
86 .join(", ");
87 format!("**{}:** {}\n\n", label, links)
88}
89
90fn render_description(output: &mut String, description: &DescriptionEntry) {
93 if let Some(title) = description.title {
94 output.push_str(&format!("#### {}\n\n", title));
95 }
96 if let Some(kind_label) = description.kind.label() {
97 output.push_str(&format!("**{}:** ", kind_label));
98 }
99 output.push_str(&format!("{}\n\n", description.description));
100
101 for (i, example) in description.examples.iter().enumerate() {
102 output.push_str(&format!("```rl\n{}\n```\n\n", example));
103 if let Some(expected) = description.expected_output.get(i) {
104 output.push_str(&format!("output:\n```text\n{}\n```\n\n", expected));
105 }
106 }
107}
108
109pub fn std_to_markdown(entries: &[&StdEntry]) -> String {
129 let mut output = String::from("# rl stdlib reference\n\n");
130 for entry in entries {
131 output.push_str(&format!("## std::{}", entry.name));
132 if let Some(since) = entry.since {
133 output.push_str(&format!(" (since {})", since));
134 }
135 if entry.unstable {
136 output.push_str(" `unstable`");
137 }
138 output.push_str("\n\n");
139 output.push_str(&format!("{}\n\n", entry.description));
140
141 for func in entry.functions {
142 output.push_str(&format!("### `{}`", func.signature));
143 if let Some(since) = func.since {
144 output.push_str(&format!(" (since {})", since));
145 }
146 output.push_str("\n\n");
147 output.push_str(&format!("{}\n\n", func.description));
148 output.push_str(&format!("**Returns:** {}\n\n", func.returns));
149 if let Some(errors) = func.errors {
150 output.push_str(&format!("**Errors:** {}\n\n", errors));
151 }
152 output.push_str(&format!("```rl\n{}\n```\n\n", func.example));
153 if let Some(expected) = func.expected_output {
154 output.push_str(&format!("output:\n```text\n{}\n```\n\n", expected));
155 }
156 output.push_str(&render_related("See also", func.see_also, ""));
157 }
158 }
159
160 output
161}
162
163fn entries_to_markdown(header: &str, entries: &[&ConceptEntry]) -> String {
165 let mut output = format!("# {}\n\n", header);
166 for entry in entries {
167 output.push_str(&format!("## {} ({})", entry.name, entry.category.label()));
168 if let Some(since) = entry.since {
169 output.push_str(&format!(" (since {})", since));
170 }
171 output.push_str("\n\n");
172 output.push_str(&format!("{}\n\n", entry.summary));
173 output.push_str(&render_related("Prerequisites", entry.prerequisites, ""));
174
175 for description in entry.descriptions {
176 render_description(&mut output, description);
177 }
178
179 if !entry.pitfalls.is_empty() {
180 output.push_str("**Pitfalls:**\n\n");
181 for pitfall in entry.pitfalls {
182 output.push_str(&format!("- {}\n", pitfall));
183 }
184 output.push('\n');
185 }
186
187 output.push_str(&render_related("Related concepts", entry.related, ""));
188 output.push_str(&render_related(
189 "Related stdlib",
190 entry.related_stdlib,
191 "std::",
192 ));
193 }
194 output
195}
196
197pub fn concept_to_markdown(entries: &[&ConceptEntry]) -> String {
199 entries_to_markdown("rl concepts reference", entries)
200}
201
202pub fn tutorial_to_markdown(entries: &[&ConceptEntry]) -> String {
204 entries_to_markdown("rl tutorial", entries)
205}
206
207#[derive(Serialize)]
208pub struct DocsJson<'a> {
209 stdlib: &'a [&'static StdEntry],
210 concepts: &'a [&'static ConceptEntry],
211 tutorial: &'a [&'static ConceptEntry],
212}
213
214pub fn docs_to_json(
215 stdlib: &[&'static StdEntry],
216 concepts: &[&'static ConceptEntry],
217 tutorial: &[&'static ConceptEntry],
218) -> Result<String, String> {
219 let doc = DocsJson {
220 stdlib,
221 concepts,
222 tutorial,
223 };
224
225 to_string_pretty(&doc).map_err(|e| e.to_string())
226}