mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-24 16:42:36 +02:00
Split out library.
This commit is contained in:
parent
5f5cf9dc7f
commit
6bac3aad01
143
exerciser/src/lib.rs
Normal file
143
exerciser/src/lib.rs
Normal file
@ -0,0 +1,143 @@
|
||||
// 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 log::{info, trace};
|
||||
use pulldown_cmark::{CowStr, Event, Parser, Tag};
|
||||
use std::{
|
||||
fs::{create_dir_all, read_to_string, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
const INCLUDE_START: &str = "{{#include ";
|
||||
const INCLUDE_END: &str = "}}";
|
||||
const FILENAME_START: &str = "<!-- File ";
|
||||
const FILENAME_END: &str = " -->";
|
||||
|
||||
pub fn process(
|
||||
input_directory: &Path,
|
||||
output_directory: &Path,
|
||||
input_contents: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let parser = Parser::new(input_contents);
|
||||
|
||||
// Find a specially-formatted comment followed by a code block, and then call `write_output`
|
||||
// with the contents of the code block, to write to a file named by the comment. Code blocks
|
||||
// without matching comments will be ignored, as will comments which are not followed by a code
|
||||
// block.
|
||||
let mut next_filename: Option<String> = None;
|
||||
let mut current_file: Option<File> = None;
|
||||
for event in parser {
|
||||
trace!("{:?}", event);
|
||||
match event {
|
||||
Event::Html(html) => {
|
||||
let html = html.trim();
|
||||
if html.starts_with(FILENAME_START) && html.ends_with(FILENAME_END) {
|
||||
next_filename = Some(
|
||||
html[FILENAME_START.len()..html.len() - FILENAME_END.len()]
|
||||
.to_string(),
|
||||
);
|
||||
info!("Next file: {:?}:", next_filename);
|
||||
}
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Writes the given output file based on the given code text from the Markdown input, processing
|
||||
/// include directives as necessary.
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Writes the given `section` (or all, if it is `None`) of the given included file (relative to the
|
||||
/// `input_directory`) to the `output_file`.
|
||||
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(())
|
||||
}
|
@ -13,21 +13,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
use anyhow::Context;
|
||||
use log::{info, trace};
|
||||
use pulldown_cmark::{CowStr, Event, Parser, Tag};
|
||||
use exerciser::process;
|
||||
use std::{
|
||||
env::args,
|
||||
fs::{create_dir, create_dir_all, read_to_string, File},
|
||||
io::Write,
|
||||
fs::{create_dir, read_to_string},
|
||||
path::Path,
|
||||
process::exit,
|
||||
};
|
||||
|
||||
const INCLUDE_START: &str = "{{#include ";
|
||||
const INCLUDE_END: &str = "}}";
|
||||
const FILENAME_START: &str = "<!-- File ";
|
||||
const FILENAME_END: &str = " -->";
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
@ -54,114 +47,8 @@ fn main() -> anyhow::Result<()> {
|
||||
.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);
|
||||
|
||||
// Find a specially-formatted comment followed by a code block, and then call `write_output`
|
||||
// with the contents of the code block, to write to a file named by the comment. Code blocks
|
||||
// without matching comments will be ignored, as will comments which are not followed by a code
|
||||
// block.
|
||||
let mut next_filename: Option<String> = None;
|
||||
let mut current_file: Option<File> = None;
|
||||
for event in parser {
|
||||
trace!("{:?}", event);
|
||||
match event {
|
||||
Event::Html(html) => {
|
||||
let html = html.trim();
|
||||
if html.starts_with(FILENAME_START) && html.ends_with(FILENAME_END) {
|
||||
next_filename = Some(
|
||||
html[FILENAME_START.len()..html.len() - FILENAME_END.len()]
|
||||
.to_string(),
|
||||
);
|
||||
info!("Next file: {:?}:", next_filename);
|
||||
}
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Writes the given output file based on the given code text from the Markdown input, processing
|
||||
/// include directives as necessary.
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Writes the given `section` (or all, if it is `None`) of the given included file (relative to the
|
||||
/// `input_directory`) to the `output_file`.
|
||||
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())?;
|
||||
}
|
||||
process(input_directory, output_directory, &input_contents)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user