Skip to content

Commit d6ca54c

Browse files
committed
add indentation detection
1 parent 2013bdc commit d6ca54c

File tree

3 files changed

+61
-51
lines changed

3 files changed

+61
-51
lines changed

src/lib.rs

+49-19
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
//!
1818
//! It will try to fetch the raw code page from the specified URL (appending
1919
//! "?raw=true"), reporting any errors. If the fetch succeeds, it will check
20-
//! that the Markdown listing is an exact substring of the GitHub listing,
21-
//! reporting any mismatch as a unified diff:
20+
//! that the Markdown listing is an exact substring of the GitHub listing
21+
//! (possibly indented by four spaces), reporting any mismatch as a unified
22+
//! diff:
2223
//!
2324
//! ```text
2425
//! tests/data/test.md: Listing `counter_2`
@@ -38,7 +39,7 @@
3839
use regex::Regex;
3940
use similar::TextDiff;
4041

41-
use std::{fs, io, path::Path, sync::LazyLock};
42+
use std::{fs, io::{self, BufRead, BufReader}, path::Path, sync::LazyLock};
4243

4344
static LISTINGS: LazyLock<Regex> = LazyLock::new(|| {
4445
Regex::new(r"(?m)^```.*?\n(?<code>[^`]+?\n)?```\n\(\[(?<title>[\s\S]+?)\]\((?<link>.*?)\)")
@@ -65,7 +66,7 @@ pub fn listings(path: impl AsRef<Path>) -> io::Result<Vec<Listing>> {
6566
let url = String::from(link.as_str());
6667
listings.push(Listing {
6768
title: String::from(title.as_str()),
68-
code: String::from(code.as_str()),
69+
local: String::from(code.as_str()),
6970
url: String::from(url.as_str()),
7071
});
7172
}
@@ -75,7 +76,7 @@ pub fn listings(path: impl AsRef<Path>) -> io::Result<Vec<Listing>> {
7576
/// A listing as parsed out of the Markdown source.
7677
pub struct Listing {
7778
pub title: String,
78-
pub code: String,
79+
pub local: String,
7980
pub url: String,
8081
}
8182

@@ -90,8 +91,8 @@ impl Listing {
9091
resp.error_for_status_ref()?;
9192
Ok(CheckedListing {
9293
title: self.title,
93-
code: self.code,
94-
text: resp.text()?,
94+
local: self.local,
95+
remote: resp.text()?,
9596
})
9697
}
9798
}
@@ -100,19 +101,48 @@ impl Listing {
100101
/// version from GitHub.
101102
pub struct CheckedListing {
102103
pub title: String,
103-
pub code: String,
104-
pub text: String,
104+
pub local: String,
105+
pub remote: String,
105106
}
106107

107-
impl CheckedListing {
108-
#[must_use]
109-
/// Diffs the Markdown listing against its canonical GitHub version.
110-
pub fn diff(&self) -> Option<String> {
111-
if self.text.contains(&self.code) {
112-
None
113-
} else {
114-
let diff = TextDiff::from_lines(&self.code, &self.text);
115-
Some(format!("{}", diff.unified_diff()))
116-
}
108+
#[must_use]
109+
/// Diffs the Markdown listing against its canonical GitHub version.
110+
pub fn diff(local: &str, remote: &str) -> Option<String> {
111+
if remote.contains(local) | remote.contains(&indent(local)) {
112+
None
113+
} else {
114+
let diff = TextDiff::from_lines(local, remote);
115+
Some(format!("{}", diff.unified_diff()))
116+
}
117+
}
118+
119+
fn indent(input: &str) -> String {
120+
let mut output = String::new();
121+
let input = input.as_bytes();
122+
let input = BufReader::new(input);
123+
for line_res in input.lines() {
124+
output.push_str(" ");
125+
output.push_str(&line_res.unwrap());
126+
output.push('\n');
127+
}
128+
output
129+
}
130+
131+
#[cfg(test)]
132+
mod tests {
133+
use super::*;
134+
135+
#[test]
136+
fn indent_indents_code_by_four_spaces() {
137+
let input = "foo\n bar\nbaz\n";
138+
let want = " foo\n bar\n baz\n";
139+
assert_eq!(indent(input), want, "wrong indentation");
140+
}
141+
142+
#[test]
143+
fn diff_matches_indented_code() {
144+
let local = "#[test]\nfn foo() {}";
145+
let remote = " #[test]\n fn foo() {}\n";
146+
assert_eq!(diff(&local, &remote), None);
117147
}
118148
}

src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::env;
22

3-
use up2code::listings;
3+
use up2code::{diff, listings};
44

55
fn main() -> anyhow::Result<()> {
66
let paths: Vec<String> = env::args().skip(1).collect();
@@ -11,7 +11,7 @@ fn main() -> anyhow::Result<()> {
1111
for path in paths {
1212
for listing in listings(&path)? {
1313
let listing = listing.check()?;
14-
if let Some(diff) = listing.diff() {
14+
if let Some(diff) = diff(&listing.local, &listing.remote) {
1515
println!("{path}: {}", listing.title);
1616
println!("{diff}");
1717
}

tests/data/test.md

+10-30
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
1-
The conventional answer to this in Rust is to put our unit tests inside a module (`mod tests`, let's say, though the name is up to us), and then protect that module with a `cfg(test)` attribute:
1+
**SOLUTION:** This is one way to do it:
22

33
\vspace{5pt}
44
```rust
5-
#[cfg(test)]
6-
mod tests {
7-
// tests go here
5+
#[test]
6+
fn append_appends_line_to_existing_file() {
7+
let dir = tempdir().unwrap();
8+
let path = dir.path().join("logbook.txt");
9+
fs::write(&path, "hello\n").unwrap();
10+
append(&path, "logbook").unwrap();
11+
let text = fs::read_to_string(path).unwrap();
12+
assert_eq!(text, "hello\nlogbook\n", "wrong text");
813
}
914
```
10-
11-
`cfg` turns compilation on or off for the item it's attached to, based on the value of some expression. `cfg(test)` means the `tests` module will be compiled only if we're running in `cargo test` mode. Otherwise it will be ignored entirely, saving time, energy, and the planet.
12-
13-
### Anatomy of a test module
14-
15-
So here's what our test looks like once we've moved it into its own module:
16-
17-
\vspace{5pt}
18-
```rust
19-
#[cfg(test)]
20-
mod tests {
21-
use std::io;
22-
23-
use super::*;
24-
25-
#[test]
26-
fn count_lines_fn_counts_lines_in_input() {
27-
let input = io::Cursor::new("line 1\nline2\n");
28-
let lines = count_lines(input);
29-
assert_eq!(2, lines);
30-
}
31-
}
32-
```
33-
([Listing `counter_2`](https://github.com/bitfield/tsr-tools/blob/main/counter_2/src/lib.rs))
34-
35-
A module can have its own `use` declarations, which is handy since we often want to `use` things in tests that we don't need in the library itself (`std::io` in this example).
15+
([Listing `logbook_4`](https://github.com/bitfield/tsr-tools/blob/main/logbook_4/src/lib.rs))

0 commit comments

Comments
 (0)