1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cspell:ignore slintdocs pipenv pipfile
5
6use anyhow::{Context, Result};
7use std::fs::create_dir_all;
8use std::io::{BufWriter, Write};
9use std::path::Path;
10use xshell::{cmd, Shell};
11
12pub fn generate() -> Result<(), Box<dyn std::error::Error>> {
13 generate_enum_docs()?;
14 generate_builtin_struct_docs()?;
15
16 let root: PathBuf = super::root_dir();
17
18 let docs_source_dir: PathBuf = root.join(path:"docs/astro");
19
20 {
21 let sh: Shell = Shell::new()?;
22 let _p: PushDir<'_> = sh.push_dir(&docs_source_dir);
23 cmd!(sh, "pnpm install --frozen-lockfile --ignore-scripts").run()?;
24 cmd!(sh, "pnpm run build").run()?;
25 }
26
27 Ok(())
28}
29
30fn write_individual_enum_files(
31 root_dir: &Path,
32 enums: &std::collections::BTreeMap<String, EnumDoc>,
33) -> Result<(), Box<dyn std::error::Error>> {
34 let enums_dir = root_dir.join("docs/astro/src/content/collections/enums");
35 create_dir_all(&enums_dir).context(format!(
36 "Failed to create folder holding individual enum doc files {enums_dir:?}"
37 ))?;
38
39 for (k, e) in enums {
40 let path = enums_dir.join(format!("{k}.md"));
41 let mut file = BufWriter::new(
42 std::fs::File::create(&path).context(format!("error creating {path:?}"))?,
43 );
44
45 write!(
46 file,
47 r#"---
48title: {0}
49description: {0} content
50---
51
52<!-- Generated with `cargo xtask slintdocs` from internal/commons/enums.rs -->
53
54`{0}`
55
56{1}
57"#,
58 k, e.description
59 )?;
60 for v in &e.values {
61 write!(
62 file,
63 r#"* **`{}`**: {}
64"#,
65 v.key, v.description
66 )?;
67 }
68 }
69 Ok(())
70}
71
72pub struct EnumValueDoc {
73 key: String,
74 description: String,
75}
76
77pub struct EnumDoc {
78 pub description: String,
79 pub values: Vec<EnumValueDoc>,
80}
81
82pub fn extract_enum_docs() -> std::collections::BTreeMap<String, EnumDoc> {
83 let mut enums: std::collections::BTreeMap<String, EnumDoc> = std::collections::BTreeMap::new();
84
85 macro_rules! gen_enums {
86 ($( $(#[doc = $enum_doc:literal])* $(#[non_exhaustive])? enum $Name:ident { $( $(#[doc = $value_doc:literal])* $Value:ident,)* })*) => {
87 $(
88 let name = stringify!($Name).to_string();
89 let mut description = String::new();
90 $( description += &format!("{}\n", $enum_doc); )*
91
92 let mut values = Vec::new();
93
94 $(
95 let mut value_docs = String::new();
96 $(
97 value_docs += $value_doc;
98 )*
99 values.push(EnumValueDoc { key: to_kebab_case(stringify!($Value)), description: value_docs });
100 )*
101
102 enums.insert(name, EnumDoc { description, values});
103 )*
104 }
105 }
106
107 #[allow(unused)] // for 'has_val'
108 {
109 i_slint_common::for_each_enums!(gen_enums);
110 }
111
112 enums
113}
114
115pub fn generate_enum_docs() -> Result<(), Box<dyn std::error::Error>> {
116 let enums: BTreeMap = extract_enum_docs();
117
118 write_individual_enum_files(&super::root_dir(), &enums)?;
119
120 Ok(())
121}
122
123pub struct StructFieldDoc {
124 key: String,
125 description: String,
126 type_name: String,
127}
128
129pub struct StructDoc {
130 pub description: String,
131 pub fields: Vec<StructFieldDoc>,
132}
133
134pub fn extract_builtin_structs() -> std::collections::BTreeMap<String, StructDoc> {
135 // `Point` should be in the documentation, but it's not inside of `for_each_builtin_structs`,
136 // so we manually create its entry first.
137 let mut structs = std::collections::BTreeMap::from([(
138 "Point".to_string(),
139 StructDoc {
140 description: "This structure represents a point with x and y coordinate".to_string(),
141 fields: vec![
142 StructFieldDoc {
143 key: "x".to_string(),
144 description: String::new(),
145 type_name: "length".to_string(),
146 },
147 StructFieldDoc {
148 key: "y".to_string(),
149 description: String::new(),
150 type_name: "length".to_string(),
151 },
152 ],
153 },
154 )]);
155
156 macro_rules! map_type {
157 (i32) => {
158 stringify!(int)
159 };
160 (f32) => {
161 stringify!(float)
162 };
163 (SharedString) => {
164 stringify!(string)
165 };
166 (Coord) => {
167 "length"
168 };
169 (Image) => {
170 "image"
171 };
172 ($pub_type:ident) => {
173 stringify!($pub_type)
174 };
175 }
176
177 macro_rules! gen_structs {
178 ($(
179 $(#[doc = $struct_doc:literal])*
180 $(#[non_exhaustive])?
181 $(#[derive(Copy, Eq)])?
182 struct $Name:ident {
183 @name = $inner_name:literal
184 export {
185 $( $(#[doc = $pub_doc:literal])* $pub_field:ident : $pub_type:ident, )*
186 }
187 private {
188 $( $(#[doc = $pri_doc:literal])* $pri_field:ident : $pri_type:ty, )*
189 }
190 }
191 )*) => {
192 $(
193 let name = stringify!($Name).to_string();
194 let mut description = String::new();
195 $(description += &format!("{}\n", $struct_doc);)*
196
197 let mut fields = Vec::new();
198 $(
199 let key = stringify!($pub_field).to_string();
200 let type_name = map_type!($pub_type).to_string();
201 let mut f_description = String::new();
202 $(
203 f_description += &format!("{}", $pub_doc);
204 )*
205 fields.push(StructFieldDoc { key, description: f_description, type_name });
206 )*
207 structs.insert(name, StructDoc { description, fields });
208 )*
209 }
210 }
211
212 i_slint_common::for_each_builtin_structs!(gen_structs);
213
214 // `StateInfo` should not be in the documentation, so remove it again
215 structs.remove("StateInfo");
216 // Experimental type
217 structs.remove("MenuEntry");
218
219 structs
220}
221
222fn write_individual_struct_files(
223 root_dir: &Path,
224 structs: std::collections::BTreeMap<String, StructDoc>,
225) -> Result<(), Box<dyn std::error::Error>> {
226 let structs_dir = root_dir.join("docs/astro/src/content/collections/structs");
227 create_dir_all(&structs_dir).context(format!(
228 "Failed to create folder holding individual structs doc files {structs_dir:?}"
229 ))?;
230
231 for (s, v) in &structs {
232 let path = structs_dir.join(format!("{s}.md"));
233 let mut file = BufWriter::new(
234 std::fs::File::create(&path).context(format!("error creating {path:?}"))?,
235 );
236
237 write!(
238 file,
239 r#"---
240title: {0}
241description: {0} content
242---
243
244<!-- Generated with `cargo xtask slintdocs` from internal/common/builtin_structs.rs -->
245
246`{0}`
247
248{1}
249"#,
250 s, v.description
251 )?;
252
253 for f in &v.fields {
254 write!(
255 file,
256 r#"- **`{}`** (_{}_): {}
257"#,
258 f.key, f.type_name, f.description
259 )?;
260 }
261 }
262
263 Ok(())
264}
265
266pub fn generate_builtin_struct_docs() -> Result<(), Box<dyn std::error::Error>> {
267 let structs: BTreeMap = extract_builtin_structs();
268 write_individual_struct_files(&super::root_dir(), structs)
269}
270
271/// Convert a ascii pascal case string to kebab case
272fn to_kebab_case(str: &str) -> String {
273 let mut result: Vec = Vec::with_capacity(str.len());
274 for x: &u8 in str.as_bytes() {
275 if x.is_ascii_uppercase() {
276 if !result.is_empty() {
277 result.push(b'-');
278 }
279 result.push(x.to_ascii_lowercase());
280 } else {
281 result.push(*x);
282 }
283 }
284 String::from_utf8(vec:result).unwrap()
285}
286