Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try generating markdown #28

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 119 additions & 19 deletions builder/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
use std::{path::Path, thread};
use std::{collections::HashMap, path::Path, str::FromStr, thread};

use eyre::{ensure, Context, OptionExt};
use eyre::{bail, ensure, Context, Ok, OptionExt};

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

let root_dir = Path::new("..")
.canonicalize()
.wrap_err("canonicalizing ..")?;
let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let examples_dir = root_dir.join("code").join("examples");

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

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

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

let mut examples = Vec::new();
for example in example_files {
let example = example.wrap_err("reading example dir entry")?;
if example
.file_type()
.wrap_err("getting file type of entry")?
.is_dir()
{
continue;
}
examples.push(example.file_name().to_str().unwrap().to_owned());
}

thread::scope(|scope| {
let mut handles = Vec::new();

for example in examples {
handles.push(scope.spawn(|| {
let example = example.wrap_err("reading example dir entry")?;
if example
.file_type()
.wrap_err("getting file type of entry")?
.is_dir()
{
return Ok(());
}

run_example(&examples_dir, &example.file_name().to_str().unwrap())
.wrap_err_with(|| format!("running {:?}", example.file_name()))
for example in &examples {
let examples_dir = &examples_dir;
handles.push(scope.spawn(move || {
run_example(&examples_dir, example)
.wrap_err_with(|| format!("running {:?}", example))
}));
}

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

let mut questions: HashMap<QName, Question> = HashMap::new();

#[derive(Default)]
struct Question {
examples: Vec<String>,
header: Option<String>,
explanation: Option<String>,
}

for example in examples {
let name = example.parse::<QName>()?;

let question = questions.entry(name).or_default();
question.examples.push(example);
}

let explanations =
std::fs::read_dir(root_dir.join("explanations")).wrap_err("failed to read explanations")?;
for expl in explanations {
let expl = expl?;
let name = expl.file_name().to_str().unwrap().parse::<QName>()?;
let expl = std::fs::read_to_string(expl.path()).wrap_err("reading explanation")?;
let Some((header, expl)) = expl.split_once('\n') else {
bail!("explanation is missing header");
};
let question = questions.entry(name).or_default();
question.header = Some(header.to_owned());
question.explanation = Some(expl.trim().to_owned());
}

let xxx = root_dir.join("xxx");
std::fs::remove_dir_all(&xxx)?;
std::fs::create_dir_all(&xxx)?;
for (qname, q) in questions {
let filename = xxx.join(format!("{}_{}.md", qname.category, qname.number));

let mut content = String::new();

let Some(header) = q.header else {
// TODO: this should be an error
continue;
};

content.push_str(&header);
content.push_str("\n\n");
content.push_str("{{#include ../src/include/quiz-is-wip.md}}\n\n");

for example in &q.examples {
content.push_str("```rust\n");
content.push_str(&format!("{{{{#include ../code/examples/{example}}}}}\n"));
content.push_str("```\n");
}

content.push_str("<details><summary>Solution</summary>\n\n");
for example in &q.examples {
content.push_str("```rust\n");
let example = example.replace(".rs", ".stderr");
content.push_str(&format!(
"{{{{#include ../code/examples/stderr/{example}}}}}\n"
));
content.push_str("```\n");
}
content.push_str("\n");
content.push_str(&q.explanation.unwrap_or_default());
content.push_str("\n\n");
content.push_str("</details>\n");

std::fs::write(filename, content).wrap_err("writing output file")?;
}

Ok(())
}

#[derive(PartialEq, Eq, Hash)]
struct QName {
category: String,
number: String,
}
impl FromStr for QName {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.split('.').next().unwrap_or_default();
let mut parts = s.split('_');
let mut category = parts
.next()
.ok_or_eyre("category missing in file name")?
.to_owned();
let mut number = parts.next().ok_or_eyre("number missing in file name")?;
if number.parse::<u16>().is_err() {
// the category has an underscore
category = format!("{category}_{number}");
number = parts.next().ok_or_eyre("number missing in file name")?;
}
Ok(Self {
category,
number: number.to_owned(),
})
}
}

fn setup_miri(dir: &Path) -> eyre::Result<()> {
eprintln!("Setting up miri");
let output = std::process::Command::new("cargo")
Expand Down
18 changes: 18 additions & 0 deletions explanations/trait_solver_1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Trait Solver 1 @BoxyUwU @WaffleLapkin

The trait implementation is for a higher ranked function pointer (`for<'a> fn`).
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.

impl:
```
type: for<'a> fn(&'a u32)
trait: Trait
```

bound:
```
type: fn(&'a u32)
trait: Trait
```

Only higher-ranked function pointers implement the trait, so it fails to compile.
31 changes: 31 additions & 0 deletions xxx/trait_solver_1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Trait Solver 1 @BoxyUwU @WaffleLapkin

{{#include ../src/include/quiz-is-wip.md}}

```rust
{{#include ../code/examples/trait_solver_1.rs}}
```
<details><summary>Solution</summary>

```rust
{{#include ../code/examples/stderr/trait_solver_1.stderr}}
```

The trait implementation is for a higher ranked function pointer (`for<'a> fn`).
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.

impl:
```
type: for<'a> fn(&'a u32)
trait: Trait
```

bound:
```
type: fn(&'a u32)
trait: Trait
```

Only higher-ranked function pointers implement the trait, so it fails to compile.

</details>