Add {{#shiftinclude}} to L/R shift on include

Syntax is the same as {{#include}} except with a shift value and colon
before the remaining arguments, e.g.

  {{#include -2:somefile.rs:myanchor}}

A positive value for the shift prepends spaces to each line.

A negative value for the shift removes chars from the beginning of each
line (including non-whitespace chars, although this will emit an error
log).
This commit is contained in:
David Drysdale 2024-03-01 11:33:59 +00:00
parent 5a35144d4f
commit 6c7b25945d
7 changed files with 1145 additions and 567 deletions

View File

@ -211,6 +211,18 @@ This is the full file.
Lines containing anchor patterns inside the included anchor are ignored.
## Including a file but changing its indentation
Sometimes it is useful to include a file or part of a file but with the indentation shifted,
using the following syntax:
```hbs
\{{#shiftinclude -2:file.rs}}
```
A positive number for the shift will prepend spaces to all lines; a negative number will remove
the corresponding number of characters from the beginning of each line.
## Including a file but initially hiding all except specified lines
The `rustdoc_include` helper is for including code from external Rust files that contain complete

View File

@ -1,9 +1,10 @@
use crate::errors::*;
use crate::utils::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
take_anchored_lines_with_shift, take_lines_with_shift, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines, Shift,
};
use regex::{CaptureMatches, Captures, Regex};
use std::cmp::Ordering;
use std::fs;
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf};
@ -20,6 +21,8 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
///
/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
///. lines, or only between the specified anchors.
/// - `{{# shiftinclude}}` - Insert content from an external file like include but shift the content
///. left or right by a specified amount.
/// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines
///. specified or the lines between specified anchors, and include the rest of the file behind `#`.
/// This hides the lines from initial display but shows them when the reader expands the code
@ -135,7 +138,7 @@ where
#[derive(PartialEq, Debug, Clone)]
enum LinkType<'a> {
Escaped,
Include(PathBuf, RangeOrAnchor),
Include(PathBuf, RangeOrAnchor, Shift),
Playground(PathBuf, Vec<&'a str>),
RustdocInclude(PathBuf, RangeOrAnchor),
Title(&'a str),
@ -206,7 +209,7 @@ impl<'a> LinkType<'a> {
let base = base.as_ref();
match self {
LinkType::Escaped => None,
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
LinkType::Include(p, _, _) => Some(return_relative_path(base, &p)),
LinkType::Playground(p, _) => Some(return_relative_path(base, &p)),
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
LinkType::Title(_) => None,
@ -257,7 +260,27 @@ fn parse_include_path(path: &str) -> LinkType<'static> {
let path = parts.next().unwrap().into();
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::Include(path, range_or_anchor)
LinkType::Include(path, range_or_anchor, Shift::None)
}
fn parse_shift_include_path(params: &str) -> LinkType<'static> {
let mut params = params.splitn(2, ':');
let param0 = params.next().unwrap();
let shift: isize = param0.parse().unwrap_or_else(|e| {
log::error!("failed to parse shift amount: {e:?}");
0
});
let shift = match shift.cmp(&0) {
Ordering::Greater => Shift::Right(shift as usize),
Ordering::Equal => Shift::None,
Ordering::Less => Shift::Left(-shift as usize),
};
let mut parts = params.next().unwrap().splitn(2, ':');
let path = parts.next().unwrap().into();
let range_or_anchor = parse_range_or_anchor(parts.next());
LinkType::Include(path, range_or_anchor, shift)
}
fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> {
@ -289,6 +312,7 @@ impl<'a> Link<'a> {
let props: Vec<&str> = path_props.collect();
match (typ.as_str(), file_arg) {
("shiftinclude", Some(pth)) => Some(parse_shift_include_path(pth)),
("include", Some(pth)) => Some(parse_include_path(pth)),
("playground", Some(pth)) => Some(LinkType::Playground(pth.into(), props)),
("playpen", Some(pth)) => {
@ -328,13 +352,17 @@ impl<'a> Link<'a> {
match self.link_type {
// omit the escape char
LinkType::Escaped => Ok(self.link_text[1..].to_owned()),
LinkType::Include(ref pat, ref range_or_anchor) => {
LinkType::Include(ref pat, ref range_or_anchor, shift) => {
let target = base.join(pat);
fs::read_to_string(&target)
.map(|s| match range_or_anchor {
RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
RangeOrAnchor::Range(range) => {
take_lines_with_shift(&s, range.clone(), shift)
}
RangeOrAnchor::Anchor(anchor) => {
take_anchored_lines_with_shift(&s, anchor, shift)
}
})
.with_context(|| {
format!(
@ -544,7 +572,8 @@ mod tests {
end_index: 48,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..20))
RangeOrAnchor::Range(LineRange::from(9..20)),
Shift::None
),
link_text: "{{#include file.rs:10:20}}",
}]
@ -563,7 +592,8 @@ mod tests {
end_index: 45,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..10))
RangeOrAnchor::Range(LineRange::from(9..10)),
Shift::None
),
link_text: "{{#include file.rs:10}}",
}]
@ -582,7 +612,8 @@ mod tests {
end_index: 46,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(9..))
RangeOrAnchor::Range(LineRange::from(9..)),
Shift::None
),
link_text: "{{#include file.rs:10:}}",
}]
@ -601,7 +632,8 @@ mod tests {
end_index: 46,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..20))
RangeOrAnchor::Range(LineRange::from(..20)),
Shift::None
),
link_text: "{{#include file.rs::20}}",
}]
@ -620,7 +652,8 @@ mod tests {
end_index: 44,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
RangeOrAnchor::Range(LineRange::from(..)),
Shift::None
),
link_text: "{{#include file.rs::}}",
}]
@ -639,7 +672,8 @@ mod tests {
end_index: 42,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
RangeOrAnchor::Range(LineRange::from(..)),
Shift::None
),
link_text: "{{#include file.rs}}",
}]
@ -658,7 +692,8 @@ mod tests {
end_index: 49,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Anchor(String::from("anchor"))
RangeOrAnchor::Anchor(String::from("anchor")),
Shift::None
),
link_text: "{{#include file.rs:anchor}}",
}]
@ -717,12 +752,12 @@ mod tests {
fn test_find_all_link_types() {
let s =
"Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \
insignifficant in escaped link}} some more\n text {{#playground my.rs editable \
insignifficant in escaped link}} some more\n shifted {{#shiftinclude -2:file.rs}} text {{#playground my.rs editable \
no_run should_panic}} ...";
let res = find_links(s).collect::<Vec<_>>();
println!("\nOUTPUT: {:?}\n", res);
assert_eq!(res.len(), 3);
assert_eq!(res.len(), 4);
assert_eq!(
res[0],
Link {
@ -730,7 +765,8 @@ mod tests {
end_index: 61,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..))
RangeOrAnchor::Range(LineRange::from(..)),
Shift::None
),
link_text: "{{#include file.rs}}",
}
@ -747,8 +783,21 @@ mod tests {
assert_eq!(
res[2],
Link {
start_index: 133,
end_index: 183,
start_index: 135,
end_index: 163,
link_type: LinkType::Include(
PathBuf::from("file.rs"),
RangeOrAnchor::Range(LineRange::from(..)),
Shift::Left(2)
),
link_text: "{{#shiftinclude -2:file.rs}}",
}
);
assert_eq!(
res[3],
Link {
start_index: 170,
end_index: 220,
link_type: LinkType::Playground(
PathBuf::from("my.rs"),
vec!["editable", "no_run", "should_panic"]
@ -765,7 +814,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
RangeOrAnchor::Range(LineRange::from(RangeFull)),
Shift::None
)
);
}
@ -777,7 +827,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
RangeOrAnchor::Range(LineRange::from(RangeFull)),
Shift::None
)
);
}
@ -789,7 +840,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
RangeOrAnchor::Range(LineRange::from(RangeFull)),
Shift::None
)
);
}
@ -801,7 +853,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(RangeFull))
RangeOrAnchor::Range(LineRange::from(RangeFull)),
Shift::None
)
);
}
@ -813,7 +866,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..5))
RangeOrAnchor::Range(LineRange::from(4..5)),
Shift::None
)
);
}
@ -825,7 +879,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
RangeOrAnchor::Range(LineRange::from(0..1)),
Shift::None
)
);
}
@ -837,7 +892,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(0..1))
RangeOrAnchor::Range(LineRange::from(0..1)),
Shift::None
)
);
}
@ -849,7 +905,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
RangeOrAnchor::Range(LineRange::from(4..)),
Shift::None
)
);
}
@ -861,7 +918,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..))
RangeOrAnchor::Range(LineRange::from(4..)),
Shift::None
)
);
}
@ -873,7 +931,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(..5))
RangeOrAnchor::Range(LineRange::from(..5)),
Shift::None
)
);
}
@ -885,7 +944,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
RangeOrAnchor::Range(LineRange::from(4..10)),
Shift::None
)
);
}
@ -897,7 +957,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5".to_string())
RangeOrAnchor::Anchor("-5".to_string()),
Shift::None
)
);
}
@ -909,7 +970,8 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("-5.7".to_string())
RangeOrAnchor::Anchor("-5.7".to_string()),
Shift::None
)
);
}
@ -921,7 +983,21 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("some-anchor".to_string())
RangeOrAnchor::Anchor("some-anchor".to_string()),
Shift::None
)
);
}
#[test]
fn parse_with_shifted_anchor() {
let link_type = parse_shift_include_path("17:arbitrary:some-anchor");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Anchor("some-anchor".to_string()),
Shift::Right(17)
)
);
}
@ -933,7 +1009,47 @@ mod tests {
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10))
RangeOrAnchor::Range(LineRange::from(4..10)),
Shift::None
)
);
}
#[test]
fn parse_start_and_end_shifted_left_range() {
let link_type = parse_shift_include_path("-2:arbitrary:5:10");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10)),
Shift::Left(2)
)
);
}
#[test]
fn parse_start_and_end_shifted_right_range() {
let link_type = parse_shift_include_path("2:arbitrary:5:10");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10)),
Shift::Right(2)
)
);
}
#[test]
fn parse_start_and_end_plus_shifted_right_range() {
let link_type = parse_shift_include_path("+2:arbitrary:5:10");
assert_eq!(
link_type,
LinkType::Include(
PathBuf::from("arbitrary"),
RangeOrAnchor::Range(LineRange::from(4..10)),
Shift::Right(2)
)
);
}

View File

@ -15,8 +15,8 @@ use std::fmt::Write;
use std::path::Path;
pub use self::string::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
take_anchored_lines, take_anchored_lines_with_shift, take_lines, take_lines_with_shift,
take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, Shift,
};
/// Replaces multiple consecutive whitespace characters with a single space character.

View File

@ -1,27 +1,63 @@
use once_cell::sync::Lazy;
use regex::Regex;
use std::borrow::Cow;
use std::ops::Bound::{Excluded, Included, Unbounded};
use std::ops::RangeBounds;
/// Indication of whether to shift included text.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Shift {
None,
Left(usize),
Right(usize),
}
fn shift_line(l: &str, shift: Shift) -> Cow<'_, str> {
match shift {
Shift::None => Cow::Borrowed(l),
Shift::Right(shift) => {
let indent = " ".repeat(shift);
Cow::Owned(format!("{indent}{l}"))
}
Shift::Left(skip) => {
if l.chars().take(skip).any(|c| !c.is_whitespace()) {
log::error!("left-shifting away non-whitespace");
}
let rest = l.chars().skip(skip).collect::<String>();
Cow::Owned(rest)
}
}
}
fn shift_lines(lines: &[String], shift: Shift) -> Vec<Cow<'_, str>> {
lines.iter().map(|l| shift_line(l, shift)).collect()
}
/// Take a range of lines from a string.
pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
take_lines_with_shift(s, range, Shift::None)
}
/// Take a range of lines from a string, shifting all lines left or right.
pub fn take_lines_with_shift<R: RangeBounds<usize>>(s: &str, range: R, shift: Shift) -> String {
let start = match range.start_bound() {
Excluded(&n) => n + 1,
Included(&n) => n,
Unbounded => 0,
};
let lines = s.lines().skip(start);
match range.end_bound() {
let retained = match range.end_bound() {
Excluded(end) => lines
.take(end.saturating_sub(start))
.collect::<Vec<_>>()
.join("\n"),
.map(|l| l.to_string())
.collect::<Vec<_>>(),
Included(end) => lines
.take((end + 1).saturating_sub(start))
.collect::<Vec<_>>()
.join("\n"),
Unbounded => lines.collect::<Vec<_>>().join("\n"),
}
.map(|l| l.to_string())
.collect::<Vec<_>>(),
Unbounded => lines.map(|l| l.to_string()).collect::<Vec<_>>(),
};
shift_lines(&retained, shift).join("\n")
}
static ANCHOR_START: Lazy<Regex> =
@ -32,7 +68,13 @@ static ANCHOR_END: Lazy<Regex> =
/// Take anchored lines from a string.
/// Lines containing anchor are ignored.
pub fn take_anchored_lines(s: &str, anchor: &str) -> String {
let mut retained = Vec::<&str>::new();
take_anchored_lines_with_shift(s, anchor, Shift::None)
}
/// Take anchored lines from a string, shifting all lines left or right.
/// Lines containing anchor are ignored.
pub fn take_anchored_lines_with_shift(s: &str, anchor: &str, shift: Shift) -> String {
let mut retained = Vec::<String>::new();
let mut anchor_found = false;
for l in s.lines() {
@ -45,7 +87,7 @@ pub fn take_anchored_lines(s: &str, anchor: &str) -> String {
}
None => {
if !ANCHOR_START.is_match(l) {
retained.push(l);
retained.push(l.to_string());
}
}
}
@ -56,7 +98,7 @@ pub fn take_anchored_lines(s: &str, anchor: &str) -> String {
}
}
retained.join("\n")
shift_lines(&retained, shift).join("\n")
}
/// Keep lines contained within the range specified as-is.
@ -118,10 +160,24 @@ pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
#[cfg(test)]
mod tests {
use super::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
take_rustdoc_include_lines,
shift_line, take_anchored_lines, take_anchored_lines_with_shift, take_lines,
take_lines_with_shift, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines,
Shift,
};
#[test]
fn shift_line_test() {
let s = " Line with 4 space intro";
assert_eq!(shift_line(s, Shift::None), s);
assert_eq!(shift_line(s, Shift::Left(4)), "Line with 4 space intro");
assert_eq!(shift_line(s, Shift::Left(2)), " Line with 4 space intro");
assert_eq!(shift_line(s, Shift::Left(6)), "ne with 4 space intro");
assert_eq!(
shift_line(s, Shift::Right(2)),
" Line with 4 space intro"
);
}
#[test]
#[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled
fn take_lines_test() {
@ -135,6 +191,56 @@ mod tests {
assert_eq!(take_lines(s, ..100), s);
}
#[test]
#[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled
fn take_lines_with_shift_test() {
let s = " Lorem\n ipsum\n dolor\n sit\n amet";
assert_eq!(
take_lines_with_shift(s, 1..3, Shift::None),
" ipsum\n dolor"
);
assert_eq!(
take_lines_with_shift(s, 1..3, Shift::Left(2)),
"ipsum\n dolor"
);
assert_eq!(
take_lines_with_shift(s, 1..3, Shift::Right(2)),
" ipsum\n dolor"
);
assert_eq!(take_lines_with_shift(s, 3.., Shift::None), " sit\n amet");
assert_eq!(
take_lines_with_shift(s, 3.., Shift::Right(1)),
" sit\n amet"
);
assert_eq!(take_lines_with_shift(s, 3.., Shift::Left(1)), " sit\n amet");
assert_eq!(
take_lines_with_shift(s, ..3, Shift::None),
" Lorem\n ipsum\n dolor"
);
assert_eq!(
take_lines_with_shift(s, ..3, Shift::Right(4)),
" Lorem\n ipsum\n dolor"
);
assert_eq!(
take_lines_with_shift(s, ..3, Shift::Left(4)),
"rem\nsum\ndolor"
);
assert_eq!(take_lines_with_shift(s, .., Shift::None), s);
// corner cases
assert_eq!(take_lines_with_shift(s, 4..3, Shift::None), "");
assert_eq!(take_lines_with_shift(s, 4..3, Shift::Left(2)), "");
assert_eq!(take_lines_with_shift(s, 4..3, Shift::Right(2)), "");
assert_eq!(take_lines_with_shift(s, ..100, Shift::None), s);
assert_eq!(
take_lines_with_shift(s, ..100, Shift::Right(2)),
" Lorem\n ipsum\n dolor\n sit\n amet"
);
assert_eq!(
take_lines_with_shift(s, ..100, Shift::Left(2)),
"Lorem\nipsum\n dolor\nsit\namet"
);
}
#[test]
fn take_anchored_lines_test() {
let s = "Lorem\nipsum\ndolor\nsit\namet";
@ -164,6 +270,144 @@ mod tests {
assert_eq!(take_anchored_lines(s, "something"), "");
}
#[test]
fn take_anchored_lines_with_shift_test() {
let s = "Lorem\nipsum\ndolor\nsit\namet";
assert_eq!(take_anchored_lines_with_shift(s, "test", Shift::None), "");
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
""
);
let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet";
assert_eq!(take_anchored_lines_with_shift(s, "test", Shift::None), "");
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
""
);
let s = " Lorem\n ipsum\n ANCHOR: test\n dolor\n sit\n amet";
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::None),
" dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
" dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
"dolor\nsit\namet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::None),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::Right(2)),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::Left(2)),
""
);
let s = " Lorem\n ipsum\n ANCHOR: test\n dolor\n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum";
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::None),
" dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
" dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
"dolor\nsit\namet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(4)),
"lor\nt\net"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(44)),
"\n\n"
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::None),
""
);
let s = " Lorem\n ANCHOR: test\n ipsum\n ANCHOR: test\n dolor\n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum";
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::None),
" ipsum\n dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
" ipsum\n dolor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
"ipsum\ndolor\nsit\namet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::None),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::Right(2)),
""
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::Left(2)),
""
);
// Include non-ASCII.
let s = " Lorem\n ANCHOR: test2\n ípsum\n ANCHOR: test\n dôlor\n sit\n amet\n ANCHOR_END: test\n lorem\n ANCHOR_END:test2\n ipsum";
assert_eq!(
take_anchored_lines_with_shift(s, "test2", Shift::None),
" ípsum\n dôlor\n sit\n amet\n lorem"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test2", Shift::Right(2)),
" ípsum\n dôlor\n sit\n amet\n lorem"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test2", Shift::Left(2)),
"ípsum\ndôlor\nsit\namet\nlorem"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test2", Shift::Left(4)),
"sum\nlor\nt\net\nrem"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::None),
" dôlor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Right(2)),
" dôlor\n sit\n amet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "test", Shift::Left(2)),
"dôlor\nsit\namet"
);
assert_eq!(
take_anchored_lines_with_shift(s, "something", Shift::None),
""
);
}
#[test]
#[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled
fn take_rustdoc_include_lines_test() {

View File

@ -9,3 +9,11 @@ assert!(!$TEST_STATUS);
// unique-string-for-anchor-test
assert!($TEST_STATUS);
// ANCHOR_END: myanchor
pub mod indent {
// ANCHOR: indentedanchor
pub fn indented_function() {
// This extra indent remains
}
// ANCHOR_END: indentedanchor
}

View File

@ -18,6 +18,12 @@ assert!($TEST_STATUS);
{{#include nested-test-with-anchors.rs:myanchor}}
```
## Includes can be shifted
```rust
{{#shiftinclude +2:nested-test-with-anchors.rs:myanchor}}
```
## Rustdoc include adds the rest of the file as hidden
```rust

File diff suppressed because it is too large Load Diff