Skip to content

Commit 667aea9

Browse files
committed
try generating markdown
1 parent 0c177ff commit 667aea9

File tree

3 files changed

+168
-19
lines changed

3 files changed

+168
-19
lines changed

builder/src/main.rs

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,47 @@
1-
use std::{path::Path, thread};
1+
use std::{collections::HashMap, path::Path, str::FromStr, thread};
22

3-
use eyre::{ensure, Context, OptionExt};
3+
use eyre::{bail, ensure, Context, Ok, OptionExt};
44

55
fn main() -> eyre::Result<()> {
66
// Ensure rustup picks up the rust-toolchain.toml file properly and doesn't get confused by this cargo run.
77
std::env::remove_var("CARGO");
88
std::env::remove_var("RUSTUP_TOOLCHAIN");
99

10-
let root_dir = Path::new("..")
11-
.canonicalize()
12-
.wrap_err("canonicalizing ..")?;
10+
let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
1311
let examples_dir = root_dir.join("code").join("examples");
1412

1513
// Change the current directory to ensure the correct rustup toolchains are used.
1614
std::env::set_current_dir(&examples_dir)
1715
.wrap_err("changing current directory to code/examples")?;
1816

19-
let examples = std::fs::read_dir(&examples_dir)
17+
let example_files = std::fs::read_dir(&examples_dir)
2018
.wrap_err("opening ../code/examples, script must be run in ./builder")?;
2119

2220
install_toolchain().wrap_err("install toolchain")?;
2321
// Setup miri to avoid race condition in `cargo miri run` below...
2422
setup_miri(&examples_dir).wrap_err("setting up miri sysroot")?;
2523

24+
let mut examples = Vec::new();
25+
for example in example_files {
26+
let example = example.wrap_err("reading example dir entry")?;
27+
if example
28+
.file_type()
29+
.wrap_err("getting file type of entry")?
30+
.is_dir()
31+
{
32+
continue;
33+
}
34+
examples.push(example.file_name().to_str().unwrap().to_owned());
35+
}
36+
2637
thread::scope(|scope| {
2738
let mut handles = Vec::new();
2839

29-
for example in examples {
30-
handles.push(scope.spawn(|| {
31-
let example = example.wrap_err("reading example dir entry")?;
32-
if example
33-
.file_type()
34-
.wrap_err("getting file type of entry")?
35-
.is_dir()
36-
{
37-
return Ok(());
38-
}
39-
40-
run_example(&examples_dir, &example.file_name().to_str().unwrap())
41-
.wrap_err_with(|| format!("running {:?}", example.file_name()))
40+
for example in &examples {
41+
let examples_dir = &examples_dir;
42+
handles.push(scope.spawn(move || {
43+
run_example(&examples_dir, example)
44+
.wrap_err_with(|| format!("running {:?}", example))
4245
}));
4346
}
4447

@@ -53,9 +56,106 @@ fn main() -> eyre::Result<()> {
5356
.for_each(|err| eprint!("error while running example: {err}"));
5457
});
5558

59+
let mut questions: HashMap<QName, Question> = HashMap::new();
60+
61+
#[derive(Default)]
62+
struct Question {
63+
examples: Vec<String>,
64+
header: Option<String>,
65+
explanation: Option<String>,
66+
}
67+
68+
for example in examples {
69+
let name = example.parse::<QName>()?;
70+
71+
let question = questions.entry(name).or_default();
72+
question.examples.push(example);
73+
}
74+
75+
let explanations =
76+
std::fs::read_dir(root_dir.join("explanations")).wrap_err("failed to read explanations")?;
77+
for expl in explanations {
78+
let expl = expl?;
79+
let name = expl.file_name().to_str().unwrap().parse::<QName>()?;
80+
let expl = std::fs::read_to_string(expl.path()).wrap_err("reading explanation")?;
81+
let Some((header, expl)) = expl.split_once('\n') else {
82+
bail!("explanation is missing header");
83+
};
84+
let question = questions.entry(name).or_default();
85+
question.header = Some(header.to_owned());
86+
question.explanation = Some(expl.trim().to_owned());
87+
}
88+
89+
let xxx = root_dir.join("xxx");
90+
std::fs::remove_dir_all(&xxx)?;
91+
std::fs::create_dir_all(&xxx)?;
92+
for (qname, q) in questions {
93+
let filename = xxx.join(format!("{}_{}.md", qname.category, qname.number));
94+
95+
let mut content = String::new();
96+
97+
let Some(header) = q.header else {
98+
// TODO: this should be an error
99+
continue;
100+
};
101+
102+
content.push_str(&header);
103+
content.push_str("\n\n");
104+
content.push_str("{{#include ../src/include/quiz-is-wip.md}}\n\n");
105+
106+
for example in &q.examples {
107+
content.push_str("```rust\n");
108+
content.push_str(&format!("{{{{#include ../code/examples/{example}}}}}\n"));
109+
content.push_str("```\n");
110+
}
111+
112+
content.push_str("<details><summary>Solution</summary>\n\n");
113+
for example in &q.examples {
114+
content.push_str("```rust\n");
115+
let example = example.replace(".rs", ".stderr");
116+
content.push_str(&format!(
117+
"{{{{#include ../code/examples/stderr/{example}}}}}\n"
118+
));
119+
content.push_str("```\n");
120+
}
121+
content.push_str("\n");
122+
content.push_str(&q.explanation.unwrap_or_default());
123+
content.push_str("\n\n");
124+
content.push_str("</details>\n");
125+
126+
std::fs::write(filename, content).wrap_err("writing output file")?;
127+
}
128+
56129
Ok(())
57130
}
58131

132+
#[derive(PartialEq, Eq, Hash)]
133+
struct QName {
134+
category: String,
135+
number: String,
136+
}
137+
impl FromStr for QName {
138+
type Err = eyre::Report;
139+
fn from_str(s: &str) -> Result<Self, Self::Err> {
140+
let s = s.split('.').next().unwrap_or_default();
141+
let mut parts = s.split('_');
142+
let mut category = parts
143+
.next()
144+
.ok_or_eyre("category missing in file name")?
145+
.to_owned();
146+
let mut number = parts.next().ok_or_eyre("number missing in file name")?;
147+
if number.parse::<u16>().is_err() {
148+
// the category has an underscore
149+
category = format!("{category}_{number}");
150+
number = parts.next().ok_or_eyre("number missing in file name")?;
151+
}
152+
Ok(Self {
153+
category,
154+
number: number.to_owned(),
155+
})
156+
}
157+
}
158+
59159
fn setup_miri(dir: &Path) -> eyre::Result<()> {
60160
eprintln!("Setting up miri");
61161
let output = std::process::Command::new("cargo")

explanations/trait_solver_1.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Trait Solver 1 @BoxyUwU @WaffleLapkin
2+
3+
The trait implementation is for a higher ranked function pointer (`for<'a> fn`).
4+
But the where clause is different, there the `for<'a>` is parsed as part of the bound, so the bound is on a *not* higher-ranked function pointer.
5+
6+
impl:
7+
```
8+
type: for<'a> fn(&'a u32)
9+
trait: Trait
10+
```
11+
12+
bound:
13+
```
14+
type: fn(&'a u32)
15+
trait: Trait
16+
```
17+
18+
Only higher-ranked function pointers implement the trait, so it fails to compile.

xxx/trait_solver_1.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Trait Solver 1 @BoxyUwU @WaffleLapkin
2+
3+
{{#include ../src/include/quiz-is-wip.md}}
4+
5+
```rust
6+
{{#include ../code/examples/trait_solver_1.rs}}
7+
```
8+
<details><summary>Solution</summary>
9+
10+
```rust
11+
{{#include ../code/examples/stderr/trait_solver_1.stderr}}
12+
```
13+
14+
The trait implementation is for a higher ranked function pointer (`for<'a> fn`).
15+
But the where clause is different, there the `for<'a>` is parsed as part of the bound, so the bound is on a *not* higher-ranked function pointer.
16+
17+
impl:
18+
```
19+
type: for<'a> fn(&'a u32)
20+
trait: Trait
21+
```
22+
23+
bound:
24+
```
25+
type: fn(&'a u32)
26+
trait: Trait
27+
```
28+
29+
Only higher-ranked function pointers implement the trait, so it fails to compile.
30+
31+
</details>

0 commit comments

Comments
 (0)