diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 10c39e3039..b7845d75ed 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -223,6 +223,7 @@ impl Page { ); context.set_shortcode_definitions(shortcode_definitions); context.set_current_page_path(&self.file.relative); + context.set_parent_absolute(&self.file.parent); context.tera_context.insert("page", &SerializingPage::new(self, None, false)); let res = render_content(&self.raw_content, &context) diff --git a/components/content/src/section.rs b/components/content/src/section.rs index 69a22a03d4..f015428cb4 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -161,6 +161,7 @@ impl Section { ); context.set_shortcode_definitions(shortcode_definitions); context.set_current_page_path(&self.file.relative); + context.set_parent_absolute(&self.file.parent); context .tera_context .insert("section", &SerializingSection::new(self, SectionSerMode::ForMarkdown)); diff --git a/components/markdown/src/codeblock/fence.rs b/components/markdown/src/codeblock/fence.rs index 474cc1729f..507ac7c8c0 100644 --- a/components/markdown/src/codeblock/fence.rs +++ b/components/markdown/src/codeblock/fence.rs @@ -25,6 +25,7 @@ pub struct FenceSettings<'a> { pub highlight_lines: Vec>, pub hide_lines: Vec>, pub name: Option<&'a str>, + pub include: Option<&'a str>, pub enable_copy: bool, } @@ -37,6 +38,7 @@ impl<'a> FenceSettings<'a> { highlight_lines: Vec::new(), hide_lines: Vec::new(), name: None, + include: None, enable_copy: false, }; @@ -48,6 +50,7 @@ impl<'a> FenceSettings<'a> { FenceToken::HighlightLines(lines) => me.highlight_lines.extend(lines), FenceToken::HideLines(lines) => me.hide_lines.extend(lines), FenceToken::Name(n) => me.name = Some(n), + FenceToken::Include(file) => me.include = Some(file), FenceToken::EnableCopy => me.enable_copy = true, } } @@ -64,6 +67,7 @@ enum FenceToken<'a> { HighlightLines(Vec>), HideLines(Vec>), Name(&'a str), + Include(&'a str), EnableCopy, } @@ -116,6 +120,11 @@ impl<'a> Iterator for FenceIter<'a> { return Some(FenceToken::Name(n)); } } + "include" => { + if let Some(file) = tok_split.next() { + return Some(FenceToken::Include(file)); + } + } "copy" => return Some(FenceToken::EnableCopy), lang => { if tok_split.next().is_some() { diff --git a/components/markdown/src/codeblock/mod.rs b/components/markdown/src/codeblock/mod.rs index a6d699db1a..777f4ea160 100644 --- a/components/markdown/src/codeblock/mod.rs +++ b/components/markdown/src/codeblock/mod.rs @@ -2,9 +2,11 @@ mod fence; mod highlight; use std::ops::RangeInclusive; +use std::path::PathBuf; use errors::{bail, Result}; use libs::syntect::util::LinesWithEndings; +use utils::fs::read_file; use crate::codeblock::highlight::SyntaxHighlighter; use config::highlighting::{resolve_syntax_and_theme, HighlightSource}; @@ -85,11 +87,12 @@ pub struct CodeBlock<'config> { line_number_start: usize, highlight_lines: Vec>, hide_lines: Vec>, + include: Option, } impl<'config> CodeBlock<'config> { pub fn new<'fence_info>( - fence: FenceSettings<'fence_info>, + fence: &FenceSettings<'fence_info>, config: &'config Config, // path to the current file if there is one, to point where the error is path: Option<&'config str>, @@ -123,13 +126,24 @@ impl<'config> CodeBlock<'config> { highlighter, line_numbers: fence.line_numbers, line_number_start: fence.line_number_start, - highlight_lines: fence.highlight_lines, - hide_lines: fence.hide_lines, + highlight_lines: fence.highlight_lines.clone(), + hide_lines: fence.hide_lines.clone(), + include: fence.include.map(|s| s.to_string()), }, html_start, )) } + pub fn include(&self, base: Option<&PathBuf>) -> Result> { + if let Some(base) = base { + let path = base.join(&self.include.as_ref().expect("No include path")); + let content = read_file(&path)?; + Ok(Some(content)) + } else { + Ok(None) + } + } + pub fn highlight(&mut self, content: &str) -> String { let mut buffer = String::new(); let mark_style = self.highlighter.mark_style(); diff --git a/components/markdown/src/context.rs b/components/markdown/src/context.rs index cefce7021a..f2bdef2759 100644 --- a/components/markdown/src/context.rs +++ b/components/markdown/src/context.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; +use std::path::PathBuf; use config::Config; use libs::tera::{Context, Tera}; @@ -13,6 +14,7 @@ pub struct RenderContext<'a> { pub config: &'a Config, pub tera_context: Context, pub current_page_path: Option<&'a str>, + pub parent_absolute: Option<&'a PathBuf>, pub current_page_permalink: &'a str, pub permalinks: Cow<'a, HashMap>, pub insert_anchor: InsertAnchor, @@ -43,6 +45,7 @@ impl<'a> RenderContext<'a> { config, lang, shortcode_definitions: Cow::Owned(HashMap::new()), + parent_absolute: None, } } @@ -57,6 +60,11 @@ impl<'a> RenderContext<'a> { self.current_page_path = Some(path); } + /// Same as above + pub fn set_parent_absolute(&mut self, path: &'a PathBuf) { + self.parent_absolute = Some(path); + } + // In use in the markdown filter // NOTE: This RenderContext is not i18n-aware, see MarkdownFilter::filter for details // If this function is ever used outside of MarkdownFilter, take this into consideration @@ -71,6 +79,7 @@ impl<'a> RenderContext<'a> { config, lang: &config.default_language, shortcode_definitions: Cow::Owned(HashMap::new()), + parent_absolute: None, } } } diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs index bd69efa93e..d63b967f05 100644 --- a/components/markdown/src/markdown.rs +++ b/components/markdown/src/markdown.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::fmt::Write; +use std::path::PathBuf; use crate::markdown::cmark::CowStr; use errors::bail; @@ -564,7 +565,7 @@ pub fn markdown_to_html( cmark::CodeBlockKind::Fenced(fence_info) => FenceSettings::new(fence_info), _ => FenceSettings::new(""), }; - let (block, begin) = match CodeBlock::new(fence, context.config, path) { + let (block, begin) = match CodeBlock::new(&fence, context.config, path) { Ok(cb) => cb, Err(e) => { error = Some(e); @@ -576,8 +577,25 @@ pub fn markdown_to_html( } Event::End(TagEnd::CodeBlock { .. }) => { if let Some(ref mut code_block) = code_block { - let html = code_block.highlight(&accumulated_block); - events.push(Event::Html(html.into())); + let maybe_inner = code_block.include( + context + .parent_absolute + .map(|e| path.map(|p| e.join(PathBuf::from(p).parent().unwrap()))) + .flatten() + .as_ref(), + ); + match maybe_inner { + Ok(i) => { + let inner = i.as_ref().unwrap_or(&accumulated_block); + let html = code_block.highlight(&inner); + events.push(Event::Html(html.into())); + } + Err(e) => { + error = Some(e); + break; + } + } + accumulated_block.clear(); }