Browse Source

Code highlighting

index-subcmd
Vincent Prouillet 7 years ago
parent
commit
a99f084ee2
5 changed files with 158 additions and 12 deletions
  1. +3
    -1
      src/cmd/serve.rs
  2. +1
    -1
      src/config.rs
  3. +1
    -0
      src/main.rs
  4. +150
    -0
      src/markdown.rs
  5. +3
    -10
      src/page.rs

+ 3
- 1
src/cmd/serve.rs View File

@@ -33,7 +33,9 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
let mut mount = Mount::new(); let mut mount = Mount::new();
mount.mount("/", Static::new(Path::new("public/"))); mount.mount("/", Static::new(Path::new("public/")));
mount.mount("/livereload.js", livereload_handler); mount.mount("/livereload.js", livereload_handler);
let server = Iron::new(mount).http(address.clone()).unwrap();
// Starts with a _ to not trigger the unused lint
// we need to assign to a variable otherwise it will block
let _iron = Iron::new(mount).http(address.clone()).unwrap();
println!("Web server is available at http://{}", address); println!("Web server is available at http://{}", address);
println!("Press CTRL+C to stop"); println!("Press CTRL+C to stop");




+ 1
- 1
src/config.rs View File

@@ -7,7 +7,7 @@ use toml::{Value as Toml, self};


use errors::{Result, ResultExt}; use errors::{Result, ResultExt};


// TODO: disable tag(s)/category(ies) page generation
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Config { pub struct Config {
/// Title of the site /// Title of the site


+ 1
- 0
src/main.rs View File

@@ -33,6 +33,7 @@ mod cmd;
mod page; mod page;
mod front_matter; mod front_matter;
mod site; mod site;
mod markdown;




fn main() { fn main() {


+ 150
- 0
src/markdown.rs View File

@@ -0,0 +1,150 @@
use std::borrow::Cow::Owned;

use pulldown_cmark as cmark;
use self::cmark::{Parser, Event, Tag};

use syntect::easy::HighlightLines;
use syntect::parsing::SyntaxSet;
use syntect::highlighting::ThemeSet;
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};


// We need to put those in a struct to impl Send and sync
struct Setup {
syntax_set: SyntaxSet,
theme_set: ThemeSet,
}

unsafe impl Send for Setup {}
unsafe impl Sync for Setup {}

lazy_static!{
static ref SETUP: Setup = Setup {
syntax_set: SyntaxSet::load_defaults_newlines(),
theme_set: ThemeSet::load_defaults()
};
}


struct CodeHighlightingParser<'a> {
// The block we're currently highlighting
highlighter: Option<HighlightLines<'a>>,
parser: Parser<'a>,
}

impl<'a> CodeHighlightingParser<'a> {
pub fn new(parser: Parser<'a>) -> CodeHighlightingParser<'a> {
CodeHighlightingParser {
highlighter: None,
parser: parser,
}
}
}

impl<'a> Iterator for CodeHighlightingParser<'a> {
type Item = Event<'a>;

fn next(&mut self) -> Option<Event<'a>> {
// Not using pattern matching to reduce indentation levels
let next_opt = self.parser.next();
if next_opt.is_none() {
return None;
}

let item = next_opt.unwrap();
// Below we just look for the start of a code block and highlight everything
// until we see the end of a code block.
// Everything else happens as normal in pulldown_cmark
match item {
Event::Text(text) => {
// if we are in the middle of a code block
if let Some(ref mut highlighter) = self.highlighter {
let highlighted = &highlighter.highlight(&text);
let html = styles_to_coloured_html(highlighted, IncludeBackground::Yes);
Some(Event::Html(Owned(html)))
} else {
Some(Event::Text(text))
}
},
Event::Start(Tag::CodeBlock(ref info)) => {
let syntax = info
.split(' ')
.next()
.and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang))
.unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text());
self.highlighter = Some(
HighlightLines::new(&syntax, &SETUP.theme_set.themes["base16-ocean.dark"])
);
let snippet = start_coloured_html_snippet(&SETUP.theme_set.themes["base16-ocean.dark"]);
Some(Event::Html(Owned(snippet)))
},
Event::End(Tag::CodeBlock(_)) => {
// reset highlight and close the code block
self.highlighter = None;
Some(Event::Html(Owned("</pre>".to_owned())))
},
_ => Some(item)
}

}
}

pub fn markdown_to_html(content: &str, highlight_code: bool) -> String {
let mut html = String::new();
if highlight_code {
let parser = CodeHighlightingParser::new(Parser::new(content));
cmark::html::push_html(&mut html, parser);
} else {
let parser = Parser::new(content);
cmark::html::push_html(&mut html, parser);
};
html
}


#[cfg(test)]
mod tests {
use super::{markdown_to_html};

#[test]
fn test_markdown_to_html_simple() {
let res = markdown_to_html("# hello", true);
assert_eq!(res, "<h1>hello</h1>\n");
}

#[test]
fn test_markdown_to_html_code_block_highlighting_off() {
let res = markdown_to_html("```\n$ gutenberg server\n```", false);
assert_eq!(
res,
"<pre><code>$ gutenberg server\n</code></pre>\n"
);
}

#[test]
fn test_markdown_to_html_code_block_no_lang() {
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true);
assert_eq!(
res,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
);
}

#[test]
fn test_markdown_to_html_code_block_with_lang() {
let res = markdown_to_html("```python\nlist.append(1)\n```", true);
assert_eq!(
res,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
);
}
#[test]
fn test_markdown_to_html_code_block_with_unknown_lang() {
let res = markdown_to_html("```yolo\nlist.append(1)\n```", true);
// defaults to plain text
assert_eq!(
res,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>"
);
}
}

+ 3
- 10
src/page.rs View File

@@ -6,7 +6,6 @@ use std::path::Path;
use std::result::Result as StdResult; use std::result::Result as StdResult;




use pulldown_cmark as cmark;
use regex::Regex; use regex::Regex;
use tera::{Tera, Context}; use tera::{Tera, Context};
use serde::ser::{SerializeStruct, self}; use serde::ser::{SerializeStruct, self};
@@ -15,6 +14,7 @@ use slug::slugify;
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use config::Config; use config::Config;
use front_matter::{FrontMatter}; use front_matter::{FrontMatter};
use markdown::markdown_to_html;




lazy_static! { lazy_static! {
@@ -22,13 +22,6 @@ lazy_static! {
static ref SUMMARY_RE: Regex = Regex::new(r"<!-- more -->").unwrap(); static ref SUMMARY_RE: Regex = Regex::new(r"<!-- more -->").unwrap();
} }


fn markdown_to_html(content: &str) -> String {
let mut html = String::new();
let parser = cmark::Parser::new(content);
cmark::html::push_html(&mut html, parser);
html
}



#[derive(Clone, Debug, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct Page { pub struct Page {
@@ -120,13 +113,13 @@ impl Page {
let mut page = Page::new(meta); let mut page = Page::new(meta);
page.filepath = filepath.to_string(); page.filepath = filepath.to_string();
page.raw_content = content.to_string(); page.raw_content = content.to_string();
page.content = markdown_to_html(&page.raw_content);
page.content = markdown_to_html(&page.raw_content, config.highlight_code.unwrap());




if page.raw_content.contains("<!-- more -->") { if page.raw_content.contains("<!-- more -->") {
page.summary = { page.summary = {
let summary = SUMMARY_RE.split(&page.raw_content).collect::<Vec<&str>>()[0]; let summary = SUMMARY_RE.split(&page.raw_content).collect::<Vec<&str>>()[0];
markdown_to_html(summary)
markdown_to_html(summary, config.highlight_code.unwrap())
} }
} }




Loading…
Cancel
Save