Skip to content

Commit 0cbc1d2

Browse files
committed
Refactoring code block extraction
1 parent 97c7a97 commit 0cbc1d2

File tree

3 files changed

+204
-110
lines changed

3 files changed

+204
-110
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ getopts = "0.2"
4545
derive-new = "0.5"
4646
cargo_metadata = "0.4"
4747

48+
[dev-dependencies]
49+
lazy_static = "1.0.0"
50+
4851
[target.'cfg(unix)'.dependencies]
4952
libc = "0.2.11"
5053

tests/system.rs

Lines changed: 200 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#![feature(rustc_private)]
1212

13+
#[macro_use]
14+
extern crate lazy_static;
1315
#[macro_use]
1416
extern crate log;
1517
extern crate regex;
@@ -19,7 +21,7 @@ extern crate term;
1921
use std::collections::HashMap;
2022
use std::fs;
2123
use std::io::{self, BufRead, BufReader, Read};
22-
use std::iter::Peekable;
24+
use std::iter::{Enumerate, Peekable};
2325
use std::path::{Path, PathBuf};
2426
use std::str::Chars;
2527

@@ -504,18 +506,191 @@ fn string_eq_ignore_newline_repr_test() {
504506
assert!(!string_eq_ignore_newline_repr("a\r\nbcd", "a\nbcdefghijk"));
505507
}
506508

507-
struct ConfigPair {
508-
name: String,
509-
value: String,
509+
enum ConfigurationSection {
510+
CodeBlock((String, u32)),
511+
ConfigName(String),
512+
ConfigValue(String),
510513
}
511514

512-
struct CodeBlock {
513-
config: ConfigPair,
514-
code_block: String,
515-
code_block_start_line: u32,
515+
impl ConfigurationSection {
516+
fn get_section<I: Iterator<Item = String>>(
517+
file: &mut Enumerate<I>,
518+
) -> Option<ConfigurationSection> {
519+
lazy_static! {
520+
static ref CONFIG_NAME_REGEX: regex::Regex = regex::Regex::new(r"^## `([^`]+)`").expect("Failed creating configuration pattern");
521+
static ref CONFIG_VALUE_REGEX: regex::Regex = regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#).expect("Failed creating configuration value pattern");
522+
}
523+
524+
loop {
525+
match file.next() {
526+
Some((i, line)) => {
527+
if line.starts_with("```rust") {
528+
// Get the lines of the code block.
529+
let lines: Vec<String> = file.map(|(_i, l)| l)
530+
.take_while(|l| !l.starts_with("```"))
531+
.collect();
532+
let block = format!("{}\n", lines.join("\n"));
533+
534+
// +1 to translate to one-based indexing
535+
// +1 to get to first line of code (line after "```")
536+
let start_line = (i + 2) as u32;
537+
538+
return Some(ConfigurationSection::CodeBlock((block, start_line)));
539+
} else if let Some(c) = CONFIG_NAME_REGEX.captures(&line) {
540+
return Some(ConfigurationSection::ConfigName(String::from(&c[1])));
541+
} else if let Some(c) = CONFIG_VALUE_REGEX.captures(&line) {
542+
return Some(ConfigurationSection::ConfigValue(String::from(&c[1])));
543+
}
544+
}
545+
None => return None, // reached the end of the file
546+
}
547+
}
548+
}
549+
}
550+
551+
// This struct stores the information about code blocks in the configurations
552+
// file, formats the code blocks, and prints formatting errors.
553+
struct ConfigCodeBlock {
554+
config_name: Option<String>,
555+
config_value: Option<String>,
556+
code_block: Option<String>,
557+
code_block_start: Option<u32>,
516558
}
517559

518-
// Read Configurations.md and build a `Vec` of `CodeBlock` structs with one
560+
impl ConfigCodeBlock {
561+
fn new() -> ConfigCodeBlock {
562+
ConfigCodeBlock {
563+
config_name: None,
564+
config_value: None,
565+
code_block: None,
566+
code_block_start: None,
567+
}
568+
}
569+
570+
fn set_config_name(&mut self, name: Option<String>) {
571+
self.config_name = name;
572+
self.config_value = None;
573+
}
574+
575+
fn set_config_value(&mut self, value: Option<String>) {
576+
self.config_value = value;
577+
}
578+
579+
fn set_code_block(&mut self, code_block: String, code_block_start: u32) {
580+
self.code_block = Some(code_block);
581+
self.code_block_start = Some(code_block_start);
582+
}
583+
584+
fn get_block_config(&self) -> Config {
585+
let mut config = Config::default();
586+
config.override_value(
587+
self.config_name.as_ref().unwrap(),
588+
self.config_value.as_ref().unwrap(),
589+
);
590+
config
591+
}
592+
593+
fn code_block_valid(&self) -> bool {
594+
// We never expect to not have a code block.
595+
assert!(self.code_block.is_some() && self.code_block_start.is_some());
596+
597+
if self.config_name.is_none() {
598+
write_message(format!(
599+
"configuration name not found for block beginning at line {}",
600+
self.code_block_start.unwrap()
601+
));
602+
return false;
603+
}
604+
if self.config_value.is_none() {
605+
write_message(format!(
606+
"configuration value not found for block beginning at line {}",
607+
self.code_block_start.unwrap()
608+
));
609+
return false;
610+
}
611+
true
612+
}
613+
614+
fn has_parsing_errors(&self, error_summary: Summary) -> bool {
615+
if error_summary.has_parsing_errors() {
616+
write_message(format!(
617+
"\u{261d}\u{1f3fd} Failed to format block starting at Line {} in Configurations.md",
618+
self.code_block_start.unwrap()
619+
));
620+
return true;
621+
}
622+
623+
false
624+
}
625+
626+
fn print_diff(&self, compare: Vec<Mismatch>) {
627+
let mut mismatches = HashMap::new();
628+
mismatches.insert(PathBuf::from("Configurations.md"), compare);
629+
print_mismatches(mismatches, |line_num| {
630+
format!(
631+
"\nMismatch at Configurations.md:{}:",
632+
line_num + self.code_block_start.unwrap() - 1
633+
)
634+
});
635+
}
636+
637+
fn formatted_has_diff(&self, file_map: FileMap) -> bool {
638+
let &(ref _file_name, ref text) = file_map.first().unwrap();
639+
let compare = make_diff(self.code_block.as_ref().unwrap(), text, DIFF_CONTEXT_SIZE);
640+
if !compare.is_empty() {
641+
self.print_diff(compare);
642+
return true;
643+
}
644+
645+
false
646+
}
647+
648+
// Return a bool indicating if formatting this code block is an idempotent
649+
// operation. This function also triggers printing any formatting failure
650+
// messages.
651+
fn formatted_is_idempotent(&self) -> bool {
652+
// Verify that we have all of the expected information.
653+
if !self.code_block_valid() {
654+
return false;
655+
}
656+
657+
let input = Input::Text(self.code_block.as_ref().unwrap().to_owned());
658+
let config = self.get_block_config();
659+
660+
let (error_summary, file_map, _report) =
661+
format_input::<io::Stdout>(input, &config, None).unwrap();
662+
663+
!self.has_parsing_errors(error_summary) && !self.formatted_has_diff(file_map)
664+
}
665+
666+
fn extract<I: Iterator<Item = String>>(
667+
file: &mut Enumerate<I>,
668+
prev: Option<&ConfigCodeBlock>,
669+
) -> Option<ConfigCodeBlock> {
670+
let mut code_block = ConfigCodeBlock::new();
671+
code_block.config_name = prev.map_or(None, |cb| cb.config_name.clone());
672+
673+
loop {
674+
match ConfigurationSection::get_section(file) {
675+
Some(ConfigurationSection::CodeBlock((block, start_line))) => {
676+
code_block.set_code_block(block, start_line);
677+
break;
678+
}
679+
Some(ConfigurationSection::ConfigName(name)) => {
680+
code_block.set_config_name(Some(name));
681+
}
682+
Some(ConfigurationSection::ConfigValue(value)) => {
683+
code_block.set_config_value(Some(value));
684+
}
685+
None => return None, // end of file was reached
686+
}
687+
}
688+
689+
Some(code_block)
690+
}
691+
}
692+
693+
// Read Configurations.md and build a `Vec` of `ConfigCodeBlock` structs with one
519694
// entry for each Rust code block found. Behavior:
520695
// - Rust code blocks are identifed by lines beginning with "```rust".
521696
// - One explicit configuration setting is supported per code block.
@@ -525,120 +700,35 @@ struct CodeBlock {
525700
// "## `NAME`".
526701
// - Configuration values in Configurations.md must be in the form of
527702
// "#### `VALUE`".
528-
fn get_code_blocks() -> Vec<CodeBlock> {
703+
fn get_code_blocks() -> Vec<ConfigCodeBlock> {
529704
let filename = "Configurations.md";
530705
let filebuf = BufReader::new(
531706
fs::File::open(filename).expect(&format!("Couldn't read file {}", filename)),
532707
);
533708

534-
let config_name_regex =
535-
regex::Regex::new(r"^## `([^`]+)`").expect("Failed creating configuration pattern");
536-
let config_value_regex = regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
537-
.expect("Failed creating configuration value pattern");
538-
539-
let mut code_blocks = Vec::new();
540-
let mut extracting_block = false;
541-
let mut code_block = Vec::new();
542-
let mut code_block_start_line = 0;
543-
let mut config_name: Option<String> = None;
544-
let mut config_value: Option<String> = None;
545-
546-
for (i, line) in filebuf.lines().map(|l| l.unwrap()).enumerate() {
547-
if line.starts_with("```") && extracting_block {
548-
// We've reached the end of the code block.
549-
extracting_block = false;
550-
551-
// Get a trailing newline in the code block.
552-
code_block.push(String::new());
553-
554-
// Store the code block information.
555-
code_blocks.push(CodeBlock {
556-
config: ConfigPair {
557-
name: config_name.as_ref().unwrap().clone(),
558-
value: config_value.unwrap(),
559-
},
560-
code_block: code_block.join("\n"),
561-
code_block_start_line,
562-
});
563-
564-
config_value = None;
565-
code_block.clear();
566-
} else if line.starts_with("```rust") {
567-
// We've reached the beginning of a Rust code block.
568-
extracting_block = true;
569-
570-
// +1 to translate to one-based indexing
571-
// +1 to get to first line of code (line after "```")
572-
code_block_start_line = (i + 2) as u32;
573-
574-
// Assert that we have a configuration name and value.
575-
assert!(
576-
config_name.is_some(),
577-
"configuration name not found for block beginning at line {}",
578-
code_block_start_line
579-
);
580-
assert!(
581-
config_value.is_some(),
582-
"configuration value not found for block beginning at line {}",
583-
code_block_start_line
584-
);
585-
} else if extracting_block {
586-
code_block.push(line);
587-
} else if let Some(c) = config_name_regex.captures(&line) {
588-
config_name = Some(String::from(&c[1]));
589-
} else if let Some(c) = config_value_regex.captures(&line) {
590-
config_value = Some(String::from(&c[1]));
591-
}
592-
}
709+
let mut code_blocks: Vec<ConfigCodeBlock> = Vec::new();
593710

594-
code_blocks
595-
}
596-
597-
// Format the code blocks extracted in `get_code_blocks` and check for
598-
// failures.
599-
fn check_blocks_idempotency(blocks: &Vec<CodeBlock>) {
600-
let mut failures = 0;
601-
602-
for block in blocks {
603-
let input = Input::Text(block.code_block.clone());
604-
let mut config = Config::default();
605-
config.override_value(&block.config.name, &block.config.value);
606-
607-
let (error_summary, file_map, _report) =
608-
format_input::<io::Stdout>(input, &config, None).unwrap();
609-
610-
if error_summary.has_parsing_errors() {
611-
write_message(String::from(format!(
612-
"\u{261d}\u{1f3fd} Failed to format block starting at Line {} in Configurations.md",
613-
block.code_block_start_line
614-
)));
615-
failures += 1;
616-
} else {
617-
for &(ref _file_name, ref text) in &file_map {
618-
let compare = make_diff(&block.code_block, text, DIFF_CONTEXT_SIZE);
619-
if !compare.is_empty() {
620-
let mut mismatches = HashMap::new();
621-
mismatches.insert(PathBuf::from("Configurations.md"), compare);
622-
print_mismatches(mismatches, |line_num| {
623-
format!(
624-
"\nMismatch at Configurations.md:{}:",
625-
line_num + block.code_block_start_line - 1
626-
)
627-
});
628-
failures += 1;
629-
}
630-
}
711+
let mut file_iter = filebuf.lines().map(|l| l.unwrap()).enumerate();
712+
loop {
713+
match ConfigCodeBlock::extract(&mut file_iter, code_blocks.last()) {
714+
Some(cb) => code_blocks.push(cb),
715+
None => break, // end of file was reached
631716
}
632717
}
633718

634-
// Display results.
635-
println!("Ran {} configurations tests.", blocks.len());
636-
assert_eq!(failures, 0, "{} configurations tests failed", failures);
719+
code_blocks
637720
}
638721

639722
#[test]
640723
#[ignore]
641724
fn configuration_snippet_tests() {
642725
let blocks = get_code_blocks();
643-
check_blocks_idempotency(&blocks);
726+
let failures = blocks
727+
.iter()
728+
.map(|b| b.formatted_is_idempotent())
729+
.fold(0, |acc, r| acc + (!r as u32));
730+
731+
// Display results.
732+
println!("Ran {} configurations tests.", blocks.len());
733+
assert_eq!(failures, 0, "{} configurations tests failed", failures);
644734
}

0 commit comments

Comments
 (0)