2019-05-26 02:50:41 +08:00
|
|
|
use crate::errors::*;
|
2019-10-06 06:27:03 +08:00
|
|
|
use crate::utils::{
|
|
|
|
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
|
|
|
|
take_rustdoc_include_lines,
|
|
|
|
};
|
2018-07-24 01:45:01 +08:00
|
|
|
use regex::{CaptureMatches, Captures, Regex};
|
2019-06-20 10:49:18 +08:00
|
|
|
use std::fs;
|
2019-07-24 08:45:43 +08:00
|
|
|
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
|
2015-12-31 05:40:23 +08:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
2018-01-08 04:05:57 +08:00
|
|
|
use super::{Preprocessor, PreprocessorContext};
|
2019-05-26 02:50:41 +08:00
|
|
|
use crate::book::{Book, BookItem};
|
2018-01-08 00:21:46 +08:00
|
|
|
|
2017-07-04 07:04:18 +08:00
|
|
|
const ESCAPE_CHAR: char = '\\';
|
2018-05-20 18:36:19 +08:00
|
|
|
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
2016-01-01 02:25:02 +08:00
|
|
|
|
2019-10-06 06:27:03 +08:00
|
|
|
/// A preprocessor for expanding helpers in a chapter. Supported helpers are:
|
|
|
|
///
|
|
|
|
/// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular
|
|
|
|
///. lines, or only between the specified anchors.
|
|
|
|
/// - `{{# 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
|
|
|
|
/// block and provides them to Rustdoc for testing.
|
|
|
|
/// - `{{# playpen}}` - Insert runnable Rust files
|
2019-05-07 02:20:58 +08:00
|
|
|
#[derive(Default)]
|
2018-01-08 04:05:57 +08:00
|
|
|
pub struct LinkPreprocessor;
|
2018-01-08 03:45:28 +08:00
|
|
|
|
|
|
|
impl LinkPreprocessor {
|
2018-09-10 18:55:58 +08:00
|
|
|
pub(crate) const NAME: &'static str = "links";
|
|
|
|
|
2018-01-21 22:35:11 +08:00
|
|
|
/// Create a new `LinkPreprocessor`.
|
2018-01-08 04:05:57 +08:00
|
|
|
pub fn new() -> Self {
|
|
|
|
LinkPreprocessor
|
2018-01-08 03:45:28 +08:00
|
|
|
}
|
2018-01-08 00:21:46 +08:00
|
|
|
}
|
|
|
|
|
2018-01-08 03:25:47 +08:00
|
|
|
impl Preprocessor for LinkPreprocessor {
|
2018-01-16 06:54:14 +08:00
|
|
|
fn name(&self) -> &str {
|
2018-09-10 18:55:58 +08:00
|
|
|
Self::NAME
|
2018-01-16 06:54:14 +08:00
|
|
|
}
|
|
|
|
|
2018-09-10 18:55:58 +08:00
|
|
|
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
|
2018-01-17 17:44:52 +08:00
|
|
|
let src_dir = ctx.root.join(&ctx.config.book.src);
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
book.for_each_mut(|section: &mut BookItem| {
|
|
|
|
if let BookItem::Chapter(ref mut ch) = *section {
|
2018-08-03 08:04:35 +08:00
|
|
|
let base = ch
|
|
|
|
.path
|
2018-01-22 06:44:28 +08:00
|
|
|
.parent()
|
|
|
|
.map(|dir| src_dir.join(dir))
|
|
|
|
.expect("All book items have a parent");
|
|
|
|
|
2018-05-20 18:36:19 +08:00
|
|
|
let content = replace_all(&ch.content, base, &ch.path, 0);
|
2018-01-22 06:44:28 +08:00
|
|
|
ch.content = content;
|
2018-01-08 00:21:46 +08:00
|
|
|
}
|
2018-01-22 06:44:28 +08:00
|
|
|
});
|
2018-01-08 00:21:46 +08:00
|
|
|
|
2018-09-10 18:55:58 +08:00
|
|
|
Ok(book)
|
2018-01-08 00:21:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-03 08:04:35 +08:00
|
|
|
fn replace_all<P1, P2>(s: &str, path: P1, source: P2, depth: usize) -> String
|
|
|
|
where
|
|
|
|
P1: AsRef<Path>,
|
|
|
|
P2: AsRef<Path>,
|
|
|
|
{
|
2017-05-19 19:04:37 +08:00
|
|
|
// When replacing one thing in a string by something with a different length,
|
|
|
|
// the indices after that will not correspond,
|
|
|
|
// we therefore have to store the difference to correct this
|
2018-01-22 06:44:28 +08:00
|
|
|
let path = path.as_ref();
|
2018-05-20 18:36:19 +08:00
|
|
|
let source = source.as_ref();
|
2016-01-01 02:25:02 +08:00
|
|
|
let mut previous_end_index = 0;
|
|
|
|
let mut replaced = String::new();
|
2015-12-31 05:40:23 +08:00
|
|
|
|
2019-07-04 17:31:04 +08:00
|
|
|
for link in find_links(s) {
|
|
|
|
replaced.push_str(&s[previous_end_index..link.start_index]);
|
2018-01-22 06:44:28 +08:00
|
|
|
|
2019-07-04 17:31:04 +08:00
|
|
|
match link.render_with_path(&path) {
|
2018-01-22 06:44:28 +08:00
|
|
|
Ok(new_content) => {
|
2018-05-20 18:36:19 +08:00
|
|
|
if depth < MAX_LINK_NESTED_DEPTH {
|
2019-07-04 17:31:04 +08:00
|
|
|
if let Some(rel_path) = link.link_type.relative_path(path) {
|
2018-12-04 07:10:09 +08:00
|
|
|
replaced.push_str(&replace_all(&new_content, rel_path, source, depth + 1));
|
2018-08-03 08:04:35 +08:00
|
|
|
} else {
|
|
|
|
replaced.push_str(&new_content);
|
2018-05-20 18:36:19 +08:00
|
|
|
}
|
2018-07-24 01:45:01 +08:00
|
|
|
} else {
|
|
|
|
error!(
|
|
|
|
"Stack depth exceeded in {}. Check for cyclic includes",
|
|
|
|
source.display()
|
|
|
|
);
|
2018-05-20 18:36:19 +08:00
|
|
|
}
|
2019-07-04 17:31:04 +08:00
|
|
|
previous_end_index = link.end_index;
|
2018-01-22 06:44:28 +08:00
|
|
|
}
|
|
|
|
Err(e) => {
|
2019-07-04 17:31:04 +08:00
|
|
|
error!("Error updating \"{}\", {}", link.link_text, e);
|
2018-09-25 19:41:38 +08:00
|
|
|
for cause in e.iter().skip(1) {
|
|
|
|
warn!("Caused By: {}", cause);
|
|
|
|
}
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
// This should make sure we include the raw `{{# ... }}` snippet
|
|
|
|
// in the page content if there are any errors.
|
2019-07-04 17:31:04 +08:00
|
|
|
previous_end_index = link.start_index;
|
2018-01-22 06:44:28 +08:00
|
|
|
}
|
|
|
|
}
|
2015-12-31 05:40:23 +08:00
|
|
|
}
|
|
|
|
|
2016-01-01 02:25:02 +08:00
|
|
|
replaced.push_str(&s[previous_end_index..]);
|
2018-01-22 06:44:28 +08:00
|
|
|
replaced
|
2017-07-04 07:04:18 +08:00
|
|
|
}
|
2016-01-01 02:25:02 +08:00
|
|
|
|
2018-01-06 05:03:30 +08:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2017-07-04 07:04:18 +08:00
|
|
|
enum LinkType<'a> {
|
|
|
|
Escaped,
|
2019-08-13 17:19:42 +08:00
|
|
|
Include(PathBuf, RangeOrAnchor),
|
2017-07-04 07:04:18 +08:00
|
|
|
Playpen(PathBuf, Vec<&'a str>),
|
2019-10-06 06:27:03 +08:00
|
|
|
RustdocInclude(PathBuf, RangeOrAnchor),
|
2015-12-31 05:40:23 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 17:19:42 +08:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
|
|
enum RangeOrAnchor {
|
|
|
|
Range(LineRange),
|
|
|
|
Anchor(String),
|
|
|
|
}
|
|
|
|
|
2019-07-24 08:45:43 +08:00
|
|
|
// A range of lines specified with some include directive.
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
|
|
enum LineRange {
|
|
|
|
Range(Range<usize>),
|
|
|
|
RangeFrom(RangeFrom<usize>),
|
|
|
|
RangeTo(RangeTo<usize>),
|
|
|
|
RangeFull(RangeFull),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RangeBounds<usize> for LineRange {
|
|
|
|
fn start_bound(&self) -> Bound<&usize> {
|
|
|
|
match self {
|
|
|
|
LineRange::Range(r) => r.start_bound(),
|
|
|
|
LineRange::RangeFrom(r) => r.start_bound(),
|
|
|
|
LineRange::RangeTo(r) => r.start_bound(),
|
|
|
|
LineRange::RangeFull(r) => r.start_bound(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn end_bound(&self) -> Bound<&usize> {
|
|
|
|
match self {
|
|
|
|
LineRange::Range(r) => r.end_bound(),
|
|
|
|
LineRange::RangeFrom(r) => r.end_bound(),
|
|
|
|
LineRange::RangeTo(r) => r.end_bound(),
|
|
|
|
LineRange::RangeFull(r) => r.end_bound(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Range<usize>> for LineRange {
|
|
|
|
fn from(r: Range<usize>) -> LineRange {
|
|
|
|
LineRange::Range(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RangeFrom<usize>> for LineRange {
|
|
|
|
fn from(r: RangeFrom<usize>) -> LineRange {
|
|
|
|
LineRange::RangeFrom(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RangeTo<usize>> for LineRange {
|
|
|
|
fn from(r: RangeTo<usize>) -> LineRange {
|
|
|
|
LineRange::RangeTo(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RangeFull> for LineRange {
|
|
|
|
fn from(r: RangeFull) -> LineRange {
|
|
|
|
LineRange::RangeFull(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 18:36:19 +08:00
|
|
|
impl<'a> LinkType<'a> {
|
|
|
|
fn relative_path<P: AsRef<Path>>(self, base: P) -> Option<PathBuf> {
|
|
|
|
let base = base.as_ref();
|
|
|
|
match self {
|
|
|
|
LinkType::Escaped => None,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(p, _) => Some(return_relative_path(base, &p)),
|
2018-07-24 01:45:01 +08:00
|
|
|
LinkType::Playpen(p, _) => Some(return_relative_path(base, &p)),
|
2019-10-06 06:27:03 +08:00
|
|
|
LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)),
|
2018-05-20 18:36:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
|
|
|
|
base.as_ref()
|
|
|
|
.join(relative)
|
|
|
|
.parent()
|
|
|
|
.expect("Included file should not be /")
|
|
|
|
.to_path_buf()
|
|
|
|
}
|
|
|
|
|
2019-08-13 17:19:42 +08:00
|
|
|
fn parse_range_or_anchor(parts: Option<&str>) -> RangeOrAnchor {
|
|
|
|
let mut parts = parts.unwrap_or("").splitn(3, ':').fuse();
|
2018-12-13 07:44:15 +08:00
|
|
|
|
|
|
|
let next_element = parts.next();
|
|
|
|
let start = if let Some(value) = next_element.and_then(|s| s.parse::<usize>().ok()) {
|
|
|
|
// subtract 1 since line numbers usually begin with 1
|
|
|
|
Some(value.saturating_sub(1))
|
2019-08-06 00:45:17 +08:00
|
|
|
} else if let Some("") = next_element {
|
|
|
|
None
|
2018-12-13 07:44:15 +08:00
|
|
|
} else if let Some(anchor) = next_element {
|
2019-08-13 17:19:42 +08:00
|
|
|
return RangeOrAnchor::Anchor(String::from(anchor));
|
2018-12-13 07:44:15 +08:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2018-01-31 18:57:47 +08:00
|
|
|
let end = parts.next();
|
2019-08-06 07:49:43 +08:00
|
|
|
// If `end` is empty string or any other value that can't be parsed as a usize, treat this
|
|
|
|
// include as a range with only a start bound. However, if end isn't specified, include only
|
|
|
|
// the single line specified by `start`.
|
|
|
|
let end = end.map(|s| s.parse::<usize>());
|
|
|
|
|
|
|
|
match (start, end) {
|
2019-08-13 17:19:42 +08:00
|
|
|
(Some(start), Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(start..end)),
|
|
|
|
(Some(start), Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(start..)),
|
|
|
|
(Some(start), None) => RangeOrAnchor::Range(LineRange::from(start..start + 1)),
|
|
|
|
(None, Some(Ok(end))) => RangeOrAnchor::Range(LineRange::from(..end)),
|
|
|
|
(None, None) | (None, Some(Err(_))) => RangeOrAnchor::Range(LineRange::from(RangeFull)),
|
2018-01-06 05:03:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-13 17:19:42 +08:00
|
|
|
fn parse_include_path(path: &str) -> LinkType<'static> {
|
|
|
|
let mut parts = path.splitn(2, ':');
|
|
|
|
|
|
|
|
let path = parts.next().unwrap().into();
|
|
|
|
let range_or_anchor = parse_range_or_anchor(parts.next());
|
|
|
|
|
|
|
|
LinkType::Include(path, range_or_anchor)
|
|
|
|
}
|
|
|
|
|
2019-10-06 06:27:03 +08:00
|
|
|
fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> {
|
|
|
|
let mut parts = path.splitn(2, ':');
|
|
|
|
|
|
|
|
let path = parts.next().unwrap().into();
|
|
|
|
let range_or_anchor = parse_range_or_anchor(parts.next());
|
|
|
|
|
|
|
|
LinkType::RustdocInclude(path, range_or_anchor)
|
|
|
|
}
|
|
|
|
|
2018-01-06 05:03:30 +08:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2017-07-04 07:04:18 +08:00
|
|
|
struct Link<'a> {
|
2016-01-01 02:25:02 +08:00
|
|
|
start_index: usize,
|
|
|
|
end_index: usize,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType<'a>,
|
2017-07-04 07:04:18 +08:00
|
|
|
link_text: &'a str,
|
2015-12-31 05:40:23 +08:00
|
|
|
}
|
|
|
|
|
2017-07-04 07:04:18 +08:00
|
|
|
impl<'a> Link<'a> {
|
|
|
|
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
|
|
|
|
let link_type = match (cap.get(0), cap.get(1), cap.get(2)) {
|
|
|
|
(_, Some(typ), Some(rest)) => {
|
|
|
|
let mut path_props = rest.as_str().split_whitespace();
|
2018-01-06 05:03:30 +08:00
|
|
|
let file_arg = path_props.next();
|
2017-07-04 07:04:18 +08:00
|
|
|
let props: Vec<&str> = path_props.collect();
|
2016-01-01 08:40:37 +08:00
|
|
|
|
2018-01-06 05:03:30 +08:00
|
|
|
match (typ.as_str(), file_arg) {
|
|
|
|
("include", Some(pth)) => Some(parse_include_path(pth)),
|
2018-12-04 07:10:09 +08:00
|
|
|
("playpen", Some(pth)) => Some(LinkType::Playpen(pth.into(), props)),
|
2019-10-06 06:27:03 +08:00
|
|
|
("rustdoc_include", Some(pth)) => Some(parse_rustdoc_include_path(pth)),
|
2017-07-04 07:04:18 +08:00
|
|
|
_ => None,
|
2016-03-18 05:31:28 +08:00
|
|
|
}
|
2017-10-03 19:40:23 +08:00
|
|
|
}
|
2018-12-04 07:10:09 +08:00
|
|
|
(Some(mat), None, None) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
2018-01-22 06:44:28 +08:00
|
|
|
Some(LinkType::Escaped)
|
|
|
|
}
|
2017-07-04 07:04:18 +08:00
|
|
|
_ => None,
|
|
|
|
};
|
2015-12-31 05:40:23 +08:00
|
|
|
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type.and_then(|lnk_type| {
|
2018-01-22 06:44:28 +08:00
|
|
|
cap.get(0).map(|mat| Link {
|
|
|
|
start_index: mat.start(),
|
|
|
|
end_index: mat.end(),
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: lnk_type,
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: mat.as_str(),
|
2018-01-06 05:03:30 +08:00
|
|
|
})
|
2017-07-04 07:04:18 +08:00
|
|
|
})
|
2015-12-31 05:40:23 +08:00
|
|
|
}
|
|
|
|
|
2017-07-04 07:04:18 +08:00
|
|
|
fn render_with_path<P: AsRef<Path>>(&self, base: P) -> Result<String> {
|
|
|
|
let base = base.as_ref();
|
2019-07-04 17:31:04 +08:00
|
|
|
match self.link_type {
|
2017-07-04 07:04:18 +08:00
|
|
|
// omit the escape char
|
|
|
|
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(ref pat, ref range_or_anchor) => {
|
2018-09-25 19:41:38 +08:00
|
|
|
let target = base.join(pat);
|
|
|
|
|
2019-06-20 10:49:18 +08:00
|
|
|
fs::read_to_string(&target)
|
2019-08-13 17:19:42 +08:00
|
|
|
.map(|s| match range_or_anchor {
|
|
|
|
RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
|
|
|
|
RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
|
2018-09-25 19:41:38 +08:00
|
|
|
})
|
2018-12-13 07:44:15 +08:00
|
|
|
.chain_err(|| {
|
|
|
|
format!(
|
|
|
|
"Could not read file for link {} ({})",
|
|
|
|
self.link_text,
|
|
|
|
target.display(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2019-10-06 06:27:03 +08:00
|
|
|
LinkType::RustdocInclude(ref pat, ref range_or_anchor) => {
|
|
|
|
let target = base.join(pat);
|
|
|
|
|
|
|
|
fs::read_to_string(&target)
|
|
|
|
.map(|s| match range_or_anchor {
|
|
|
|
RangeOrAnchor::Range(range) => {
|
|
|
|
take_rustdoc_include_lines(&s, range.clone())
|
|
|
|
}
|
|
|
|
RangeOrAnchor::Anchor(anchor) => {
|
|
|
|
take_rustdoc_include_anchored_lines(&s, anchor)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.chain_err(|| {
|
|
|
|
format!(
|
|
|
|
"Could not read file for link {} ({})",
|
|
|
|
self.link_text,
|
|
|
|
target.display(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2017-07-04 07:04:18 +08:00
|
|
|
LinkType::Playpen(ref pat, ref attrs) => {
|
2018-09-25 19:41:38 +08:00
|
|
|
let target = base.join(pat);
|
|
|
|
|
2019-06-20 10:49:18 +08:00
|
|
|
let contents = fs::read_to_string(&target).chain_err(|| {
|
2018-12-04 07:10:09 +08:00
|
|
|
format!(
|
|
|
|
"Could not read file for link {} ({})",
|
|
|
|
self.link_text,
|
|
|
|
target.display()
|
|
|
|
)
|
|
|
|
})?;
|
2017-07-04 07:04:18 +08:00
|
|
|
let ftype = if !attrs.is_empty() { "rust," } else { "rust" };
|
2018-01-06 05:03:30 +08:00
|
|
|
Ok(format!(
|
|
|
|
"```{}{}\n{}\n```\n",
|
|
|
|
ftype,
|
|
|
|
attrs.join(","),
|
|
|
|
contents
|
|
|
|
))
|
2017-10-03 19:40:23 +08:00
|
|
|
}
|
2017-07-04 07:04:18 +08:00
|
|
|
}
|
|
|
|
}
|
2015-12-31 05:40:23 +08:00
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2017-07-04 07:04:18 +08:00
|
|
|
struct LinkIter<'a>(CaptureMatches<'a, 'a>);
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2017-07-04 07:04:18 +08:00
|
|
|
impl<'a> Iterator for LinkIter<'a> {
|
|
|
|
type Item = Link<'a>;
|
|
|
|
fn next(&mut self) -> Option<Link<'a>> {
|
|
|
|
for cap in &mut self.0 {
|
|
|
|
if let Some(inc) = Link::from_capture(cap) {
|
|
|
|
return Some(inc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2019-05-07 04:50:34 +08:00
|
|
|
fn find_links(contents: &str) -> LinkIter<'_> {
|
2017-07-04 07:04:18 +08:00
|
|
|
// lazily compute following regex
|
|
|
|
// r"\\\{\{#.*\}\}|\{\{#([a-zA-Z0-9]+)\s*([a-zA-Z0-9_.\-:/\\\s]+)\}\}")?;
|
|
|
|
lazy_static! {
|
2018-07-24 01:45:01 +08:00
|
|
|
static ref RE: Regex = Regex::new(
|
|
|
|
r"(?x) # insignificant whitespace mode
|
|
|
|
\\\{\{\#.*\}\} # match escaped link
|
|
|
|
| # or
|
|
|
|
\{\{\s* # link opening parens and whitespace
|
2019-10-06 06:27:03 +08:00
|
|
|
\#([a-zA-Z0-9_]+) # link type
|
2018-07-24 01:45:01 +08:00
|
|
|
\s+ # separating whitespace
|
|
|
|
([a-zA-Z0-9\s_.\-:/\\]+) # link target path and space separated properties
|
|
|
|
\s*\}\} # whitespace and link closing parens"
|
2019-05-05 22:57:43 +08:00
|
|
|
)
|
|
|
|
.unwrap();
|
2017-07-04 07:04:18 +08:00
|
|
|
}
|
|
|
|
LinkIter(RE.captures_iter(contents))
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-08-03 08:04:35 +08:00
|
|
|
#[test]
|
|
|
|
fn test_replace_all_escaped() {
|
|
|
|
let start = r"
|
|
|
|
Some text over here.
|
|
|
|
```hbs
|
|
|
|
\{{#include file.rs}} << an escaped link!
|
|
|
|
```";
|
|
|
|
let end = r"
|
|
|
|
Some text over here.
|
|
|
|
```hbs
|
|
|
|
{{#include file.rs}} << an escaped link!
|
|
|
|
```";
|
|
|
|
assert_eq!(replace_all(start, "", "", 0), end);
|
|
|
|
}
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_no_link() {
|
|
|
|
let s = "Some random text without link...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_partial_link() {
|
|
|
|
let s = "Some random text with {{#playpen...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
let s = "Some random text with {{#include...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
let s = "Some random text with \\{{#include...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_empty_link() {
|
|
|
|
let s = "Some random text with {{#playpen}} and {{#playpen }} {{}} {{#}}...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_unknown_link_type() {
|
|
|
|
let s = "Some random text with {{#playpenz ar.rs}} and {{#incn}} {{baz}} {{#bar}}...";
|
|
|
|
assert!(find_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_simple_link() {
|
|
|
|
let s = "Some random text with {{#playpen file.rs}} and {{#playpen test.rs }}...";
|
2016-01-01 08:40:37 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
2018-01-06 05:03:30 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
vec![
|
|
|
|
Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 42,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Playpen(PathBuf::from("file.rs"), vec![]),
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: "{{#playpen file.rs}}",
|
|
|
|
},
|
|
|
|
Link {
|
|
|
|
start_index: 47,
|
|
|
|
end_index: 68,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Playpen(PathBuf::from("test.rs"), vec![]),
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: "{{#playpen test.rs }}",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2018-01-06 05:03:30 +08:00
|
|
|
|
2020-05-03 20:42:22 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_special_characters() {
|
|
|
|
let s = "Some random text with {{#playpen foo-bar\\baz/_c++.rs}}...";
|
|
|
|
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 54,
|
|
|
|
link_type: LinkType::Playpen(PathBuf::from("foo-bar\\baz/_c++.rs"), vec![]),
|
|
|
|
link_text: "{{#playpen foo-bar\\baz/_c++.rs}}",
|
|
|
|
},]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_range() {
|
|
|
|
let s = "Some random text with {{#include file.rs:10:20}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 48,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(9..20))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs:10:20}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
2018-01-06 05:03:30 +08:00
|
|
|
|
2018-01-31 18:57:47 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_line_number() {
|
|
|
|
let s = "Some random text with {{#include file.rs:10}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 45,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(9..10))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs:10}}",
|
|
|
|
}]
|
2018-01-31 18:57:47 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_from_range() {
|
|
|
|
let s = "Some random text with {{#include file.rs:10:}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 46,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(9..))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs:10:}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
2018-01-06 05:03:30 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_to_range() {
|
|
|
|
let s = "Some random text with {{#include file.rs::20}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 46,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(..20))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs::20}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
2016-01-01 08:40:37 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_full_range() {
|
|
|
|
let s = "Some random text with {{#include file.rs::}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 44,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(..))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs::}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
2017-07-04 07:04:18 +08:00
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_no_range_specified() {
|
|
|
|
let s = "Some random text with {{#include file.rs}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 42,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(..))
|
|
|
|
),
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "{{#include file.rs}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
2017-07-04 07:04:18 +08:00
|
|
|
|
2018-12-13 07:44:15 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_with_anchor() {
|
|
|
|
let s = "Some random text with {{#include file.rs:anchor}}...";
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
vec![Link {
|
|
|
|
start_index: 22,
|
|
|
|
end_index: 49,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
2018-12-13 07:44:15 +08:00
|
|
|
PathBuf::from("file.rs"),
|
2019-08-13 17:19:42 +08:00
|
|
|
RangeOrAnchor::Anchor(String::from("anchor"))
|
2018-12-13 07:44:15 +08:00
|
|
|
),
|
|
|
|
link_text: "{{#include file.rs:anchor}}",
|
|
|
|
}]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-01-22 06:44:28 +08:00
|
|
|
#[test]
|
|
|
|
fn test_find_links_escaped_link() {
|
|
|
|
let s = "Some random text with escaped playpen \\{{#playpen file.rs editable}} ...";
|
|
|
|
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
2018-07-24 01:45:01 +08:00
|
|
|
vec![Link {
|
|
|
|
start_index: 38,
|
|
|
|
end_index: 68,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Escaped,
|
2018-07-24 01:45:01 +08:00
|
|
|
link_text: "\\{{#playpen file.rs editable}}",
|
|
|
|
}]
|
2018-01-22 06:44:28 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_playpens_with_properties() {
|
|
|
|
let s = "Some random text with escaped playpen {{#playpen file.rs editable }} and some \
|
|
|
|
more\n text {{#playpen my.rs editable no_run should_panic}} ...";
|
|
|
|
|
|
|
|
let res = find_links(s).collect::<Vec<_>>();
|
|
|
|
println!("\nOUTPUT: {:?}\n", res);
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
vec![
|
|
|
|
Link {
|
|
|
|
start_index: 38,
|
|
|
|
end_index: 68,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Playpen(PathBuf::from("file.rs"), vec!["editable"]),
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: "{{#playpen file.rs editable }}",
|
|
|
|
},
|
|
|
|
Link {
|
|
|
|
start_index: 89,
|
|
|
|
end_index: 136,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Playpen(
|
2018-01-22 06:44:28 +08:00
|
|
|
PathBuf::from("my.rs"),
|
|
|
|
vec!["editable", "no_run", "should_panic"],
|
|
|
|
),
|
2018-12-04 07:10:09 +08:00
|
|
|
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
2018-01-22 06:44:28 +08:00
|
|
|
},
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_find_all_link_types() {
|
|
|
|
let s = "Some random text with escaped playpen {{#include file.rs}} and \\{{#contents are \
|
|
|
|
insignifficant in escaped link}} some more\n text {{#playpen 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[0],
|
|
|
|
Link {
|
|
|
|
start_index: 38,
|
|
|
|
end_index: 58,
|
2019-08-13 17:19:42 +08:00
|
|
|
link_type: LinkType::Include(
|
|
|
|
PathBuf::from("file.rs"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(..))
|
|
|
|
),
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: "{{#include file.rs}}",
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
res[1],
|
|
|
|
Link {
|
|
|
|
start_index: 63,
|
|
|
|
end_index: 112,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Escaped,
|
2018-01-22 06:44:28 +08:00
|
|
|
link_text: "\\{{#contents are insignifficant in escaped link}}",
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
res[2],
|
|
|
|
Link {
|
|
|
|
start_index: 130,
|
|
|
|
end_index: 177,
|
2019-07-04 17:31:04 +08:00
|
|
|
link_type: LinkType::Playpen(
|
2018-01-22 06:44:28 +08:00
|
|
|
PathBuf::from("my.rs"),
|
|
|
|
vec!["editable", "no_run", "should_panic"]
|
|
|
|
),
|
|
|
|
link_text: "{{#playpen my.rs editable no_run should_panic}}",
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2016-01-01 08:40:37 +08:00
|
|
|
|
2019-08-05 09:52:14 +08:00
|
|
|
#[test]
|
|
|
|
fn parse_without_colon_includes_all() {
|
|
|
|
let link_type = parse_include_path("arbitrary");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(RangeFull))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_nothing_after_colon_includes_all() {
|
|
|
|
let link_type = parse_include_path("arbitrary:");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(RangeFull))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_two_colons_includes_all() {
|
|
|
|
let link_type = parse_include_path("arbitrary::");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(RangeFull))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_garbage_after_two_colons_includes_all() {
|
|
|
|
let link_type = parse_include_path("arbitrary::NaN");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(RangeFull))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_one_number_after_colon_only_that_line() {
|
|
|
|
let link_type = parse_include_path("arbitrary:5");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(4..5))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_one_based_start_becomes_zero_based() {
|
|
|
|
let link_type = parse_include_path("arbitrary:1");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(0..1))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_zero_based_start_stays_zero_based_but_is_probably_an_error() {
|
|
|
|
let link_type = parse_include_path("arbitrary:0");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(0..1))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_start_only_range() {
|
|
|
|
let link_type = parse_include_path("arbitrary:5:");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(4..))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_start_with_garbage_interpreted_as_start_only_range() {
|
|
|
|
let link_type = parse_include_path("arbitrary:5:NaN");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(4..))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_end_only_range() {
|
|
|
|
let link_type = parse_include_path("arbitrary::5");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(..5))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_start_and_end_range() {
|
|
|
|
let link_type = parse_include_path("arbitrary:5:10");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(4..10))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_negative_interpreted_as_anchor() {
|
|
|
|
let link_type = parse_include_path("arbitrary:-5");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Anchor("-5".to_string())
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_floating_point_interpreted_as_anchor() {
|
|
|
|
let link_type = parse_include_path("arbitrary:-5.7");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Anchor("-5.7".to_string())
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_anchor_followed_by_colon() {
|
|
|
|
let link_type = parse_include_path("arbitrary:some-anchor:this-gets-ignored");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Anchor("some-anchor".to_string())
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_with_more_than_three_colons_ignores_everything_after_third_colon() {
|
|
|
|
let link_type = parse_include_path("arbitrary:5:10:17:anything:");
|
|
|
|
assert_eq!(
|
|
|
|
link_type,
|
2019-08-13 17:19:42 +08:00
|
|
|
LinkType::Include(
|
|
|
|
PathBuf::from("arbitrary"),
|
|
|
|
RangeOrAnchor::Range(LineRange::from(4..10))
|
|
|
|
)
|
2019-08-05 09:52:14 +08:00
|
|
|
);
|
|
|
|
}
|
2015-12-31 19:00:09 +08:00
|
|
|
}
|