1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-23 07:06:19 +02:00

Add utility to build exercise starting directory from Markdown.

This commit is contained in:
Andrew Walbran 2023-03-30 12:42:21 +01:00
parent b85c893390
commit 89c8eb0ef2
4 changed files with 310 additions and 0 deletions

144
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-example"
version = "0.1.0"
@ -9,6 +18,23 @@ dependencies = [
"buddy_system_allocator",
]
[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -167,6 +193,19 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.0"
@ -188,6 +227,16 @@ dependencies = [
"libc",
]
[[package]]
name = "exerciser"
version = "0.1.0"
dependencies = [
"anyhow",
"log",
"pretty_env_logger",
"pulldown-cmark",
]
[[package]]
name = "fastrand"
version = "1.9.0"
@ -350,6 +399,15 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -413,6 +471,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "hyper"
version = "0.14.25"
@ -839,6 +906,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@ -854,6 +931,23 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.26"
@ -962,6 +1056,23 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "reqwest"
version = "0.11.16"
@ -1263,6 +1374,15 @@ dependencies = [
"utf-8",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.40"
@ -1370,6 +1490,15 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"
@ -1420,6 +1549,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.0"
@ -1534,6 +1669,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"exerciser",
"src/exercises",
"src/bare-metal/useful-crates/allocator-example",
"src/bare-metal/useful-crates/zerocopy-example",

14
exerciser/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "exerciser"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
authors = ["Andrew Walbran <qwandor@google.com>"]
description = "A tool for extracting starter code for exercises from Markdown files."
repository = "https://github.com/google/comprehensive-rust"
[dependencies]
anyhow = "1.0.68"
log = "0.4.17"
pretty_env_logger = "0.4.0"
pulldown-cmark = { version = "0.9.2", default-features = false }

151
exerciser/src/main.rs Normal file
View File

@ -0,0 +1,151 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use anyhow::Context;
use log::{info, trace};
use pulldown_cmark::{CowStr, Event, Parser, Tag};
use std::{
env::args,
fs::{create_dir, create_dir_all, read_to_string, File},
io::Write,
path::Path,
process::exit,
};
const INCLUDE_START: &str = "{{#include ";
const INCLUDE_END: &str = "}}";
fn main() -> anyhow::Result<()> {
pretty_env_logger::init();
let args = args().collect::<Vec<_>>();
if args.len() != 3 {
eprintln!("Usage:");
eprintln!(
" {} <src/exercises/exercise.md> <output directory>",
args[0]
);
exit(1);
}
let input_filename = Path::new(&args[1]);
let output_directory = Path::new(&args[2]);
create_dir(output_directory).with_context(|| {
format!("Failed to create output directory {:?}", output_directory)
})?;
let input_directory = input_filename
.parent()
.with_context(|| "Input file has no parent directory.")?;
let input_contents = read_to_string(input_filename)
.with_context(|| format!("Failed to open {:?}", input_filename))?;
let parser = Parser::new(&input_contents);
let mut next_filename: Option<String> = None;
let mut current_file: Option<File> = None;
for event in parser {
trace!("{:?}", event);
match event {
Event::Code(x) => {
info!("{}:", x);
next_filename = Some(x.into_string());
}
Event::Start(Tag::CodeBlock(x)) => {
info!("Start {:?}", x);
if let Some(filename) = &next_filename {
let full_filename = output_directory.join(filename);
info!("Opening {:?}", full_filename);
if let Some(directory) = full_filename.parent() {
create_dir_all(directory)?;
}
current_file = Some(File::create(full_filename)?);
next_filename = None;
}
}
Event::Text(text) => {
info!("Text: {:?}", text);
if let Some(output_file) = &mut current_file {
write_output(text, input_directory, output_file)?;
}
}
Event::End(Tag::CodeBlock(x)) => {
info!("End {:?}", x);
current_file = None;
}
_ => {}
}
}
Ok(())
}
fn write_output(
text: CowStr,
input_directory: &Path,
output_file: &mut File,
) -> anyhow::Result<()> {
for line in text.lines() {
info!("Line: {:?}", line);
if let (Some(start), Some(end)) =
(line.find(INCLUDE_START), line.find(INCLUDE_END))
{
let include = line[start + INCLUDE_START.len()..end].trim();
info!("Include {:?}", include);
if let Some(colon) = include.find(":") {
write_include(
&include[0..colon],
Some(&include[colon + 1..]),
input_directory,
output_file,
)?;
} else {
write_include(include, None, input_directory, output_file)?;
}
} else {
output_file.write(line.as_bytes())?;
output_file.write(b"\n")?;
}
}
Ok(())
}
fn write_include(
include_filename: &str,
section: Option<&str>,
input_directory: &Path,
output_file: &mut File,
) -> anyhow::Result<()> {
let full_include_filename = input_directory.join(include_filename);
let input_file = read_to_string(full_include_filename)?;
if let Some(section) = section {
let start_anchor = format!("ANCHOR: {}", section);
let end_anchor = format!("ANCHOR_END: {}", section);
for line in input_file
.lines()
.skip_while(|line| !line.contains(&start_anchor))
.skip(1)
.take_while(|line| !line.contains(&end_anchor))
{
output_file.write(line.as_bytes())?;
output_file.write(b"\n")?;
}
} else {
output_file.write(input_file.as_bytes())?;
}
Ok(())
}