rustyphoenixlecture 1.7.2

This project aims to provide a simple a powerfull lecture compilation to generate html web sites
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::{collections::HashMap, fs, path::PathBuf};
use serde::Deserialize;
use toml::{self, de::Error};

use crate::phighlighter::{
	phighlighter::PHighlighter,
	planguage::PLanguage,
};

///Configuration of a Highlighter
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterMainConfig{
	///Say if the highlighter will display line number
	is_line_number: bool,
	///All characters which define a token
	token_charset: String,
	///Vector of accepted extensions for highlighting this particular language
	vec_extention: Vec<String>,
	///Vector of accepted filenames for highlithing this particular language (CMakeList.txt for cmake, Dockerfile for docker, etc)
	vec_filename: Vec<String>,
	///Definition of a single line comment in this language
	single_line_comment: String,
	///Begining of a multi-line comment in this language
	multi_line_comment_begin: String,
	///Ending of a multi-line comment in this language
	multi_line_comment_end: String,
	///Say if the current parser has an escape char, false otherwise
	is_escape_char: bool,
	///Example of the language to be parsed
	example: String,
}

///Configuration of a Highlighter keyword
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterKeyword{
	///CSS style to be applied on all keywords
	style: String,
	///Vector of token with the same style
	vec_token: Vec<String>,
	///Vector of matches expression (not token with token check)
	vec_match: Vec<String>,
}

///Configuration of a Highlighter getuntil
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterGetUntil{
	///CSS style to be applied on all getunil
	pub style: String,
	///Starting token of the GetUntil
	pub start_token: String,
	///End token of the GetUntil
	pub end_token: String,
}

///Configuration of a Highlighter getuntilreplace
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterGetUntilReplace{
	///Starting token of the GetUntilReplace
	pub start_token: String,
	///End token of the GetUntilReplace
	pub end_token: String,
	///String which will replace the start_token
	pub replace_start: String,
	///String which will replace the end_token
	pub replace_end: String,
}

///Configuration of a Highlighter sequence step
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterStep{
	///Define a token which has to be matched but not followed by a char in the token_charset
	pub token: Option<String>,
	///Define a token which has to be matched, and we don't care what is following
	pub keyword: Option<String>,
	///Define a set of characters which has to be matched in any order
	pub oneof: Option<String>,
}

///Configuration of a Highlighter sequence
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterSequence{
	///CSS style to be applied on all steps of sequence
	pub style: String,
	///Vector of all steps of the sequence
	pub vec_step: Vec<PHighlighterStep>,
}

///Configuration of a Highlighter
#[derive(Debug, Clone, Deserialize)]
pub struct PHighlighterConfig{
	///Main configuration of the highlighter
	highlighting: PHighlighterMainConfig,
	///Vector of all keyword style
	keyword: Option<Vec<PHighlighterKeyword>>,
	///Vector of all replace style
	replace: Option<HashMap<String, String>>,
	///Vector of all getuntil style
	getuntil: Option<Vec<PHighlighterGetUntil>>,
	///Vector of all getuntil style
	getuntilreplace: Option<Vec<PHighlighterGetUntilReplace>>,
	///Vector of all parsing sequence of the highlighter
	sequence: Option<Vec<PHighlighterSequence>>,
}

///Load the highlighter configuration file
/// # Parameters
/// - `filename` : name of the highlighter configuration file (in toml)
/// # Returns
/// Replace parsed highlighter configuration or error on fail
pub fn load_highlighter(filename: &PathBuf) -> Result<PHighlighterConfig, Error> {
	let config: PHighlighterConfig = match fs::read_to_string(filename) {
		Ok(str) => match toml::from_str(&str){
			Ok(value) => value,
			Err(error) => panic!("load_highlighter : cannot parse file {:?}, parsing error {}", filename, error)
		},
		Err(err) => panic!("load_highlighter : cannot read file {:?}, error: {}", filename, err)
	};
	Ok(config)
}

///Load the highlighter from a configuration file
/// # Parameters
/// - `filename` : name of the highlighter configuration file (in toml)
/// # Returns
/// Replace parsed highlighter configuration or error on fail
pub fn load_node_highlight(filename: &PathBuf) -> PHighlighter{
	let config: PHighlighterConfig = match load_highlighter(filename) {
		Ok(value) => value,
		Err(err) => panic!("load_node_highlight : cannot load highlighter configuration {:?}. Error {}", filename, err)
	};
	let mut language = PLanguage::new(config.highlighting.is_line_number, config.highlighting.token_charset);
	language.set_vec_extension(&config.highlighting.vec_extention);
	language.set_vec_filename(&config.highlighting.vec_filename);
	language.set_single_line_comment(&config.highlighting.single_line_comment);
	language.set_multi_line_comment_begin(&config.highlighting.multi_line_comment_begin);
	language.set_multi_line_comment_end(&config.highlighting.multi_line_comment_end);
	language.set_is_escape_char(config.highlighting.is_escape_char);
	language.set_example(&config.highlighting.example);
	
	let mut highlighter: PHighlighter = PHighlighter::new();
	highlighter.set_language(&language);
	//Add all keywords
	match &config.keyword {
		Some(vec_keyword) => {
			for keyword in vec_keyword.iter(){	//Let's iterate on all keywork configuration
				let css_style = keyword.style.clone();
				//Let's add the vector of token in the highlighter
				highlighter.add_vec_token(&keyword.vec_token, &css_style);
				highlighter.add_vec_match(&keyword.vec_match, &css_style);
			}
		},
		None => {}
	};
	//Add all replace
	match &config.replace {
		Some(map_replace) => {
			for (pattern, replace) in map_replace.iter() {
				highlighter.add_replace(&pattern, &replace);
			}
		},
		None => {}
	};
	//We have to do the same for getuntil
	match &config.getuntil {
		Some(getuntil) => highlighter.add_vec_getuntil(getuntil),
		None => {}
	};
	//We have to do the same for getuntil
	match &config.getuntilreplace {
		Some(getuntilreplace) => highlighter.add_vec_getuntilreplace(getuntilreplace),
		None => {}
	};
	//We have to do the same for sequence
	match &config.sequence {
		Some(sequence) => highlighter.add_vec_sequence(sequence),
		None => {}
	};
	return highlighter;
}

#[cfg(test)]
mod tests{
	use super::*;
	
	///Test the load highlighter config
	#[test]
	fn test_load_highlighter_config(){
		let parser_cpp: PHighlighterConfig = load_highlighter(&PathBuf::from("tests/parser/cpp.toml")).unwrap();
		assert_eq!(parser_cpp.highlighting.is_line_number, true);
		assert_eq!(parser_cpp.highlighting.token_charset, String::from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"));
		assert_eq!(parser_cpp.highlighting.single_line_comment, String::from("//"));
		assert_eq!(parser_cpp.highlighting.multi_line_comment_begin, String::from("/*"));
		assert_eq!(parser_cpp.highlighting.multi_line_comment_end, String::from("*/"));
		assert_eq!(parser_cpp.keyword.unwrap().len(), 20);
	}
	///Test the load highlighter
	#[test]
	fn test_load_highlighter(){
		let parser_cpp: PHighlighter = load_node_highlight(&PathBuf::from("tests/parser/cpp.toml"));
		//Test of a simple prototype highlighting
		assert_eq!(parser_cpp.highlight(&String::from("int function(float base, size_t index);")), String::from("<span class=\"dsType\">int</span> function(<span class=\"dsType\">float</span> base, <span class=\"dsType\">size_t</span> index);"));
		//Test of a simple string highlighting
		assert_eq!(parser_cpp.highlight(&String::from("std::string name(\"shadoko\");")), String::from("<span class=\"cppstdF\">std</span>::<span class=\"cppstdF\">string</span> name(<span class=\"dsString\">&quot;shadoko&quot;</span>);"));
		assert_eq!(parser_cpp.highlight(&String::from("int value = 42;")), String::from("<span class=\"dsType\">int</span> value = <span class=\"dsNumber\">42</span>;"));
		assert_eq!(parser_cpp.highlight(&String::from("int value = 4;")), String::from("<span class=\"dsType\">int</span> value = <span class=\"dsNumber\">4</span>;"));
		assert_eq!(parser_cpp.highlight(&String::from("return 1;")), String::from("<span class=\"dsKeyword\">return</span> <span class=\"dsNumber\">1</span>;"));
		assert_eq!(parser_cpp.highlight(&String::from("return 10;")), String::from("<span class=\"dsKeyword\">return</span> <span class=\"dsNumber\">10</span>;"));
		//Let's check hexadecimal and binary
		assert_eq!(parser_cpp.highlight(&String::from("int value = 0xff;")), String::from("<span class=\"dsType\">int</span> value = <span class=\"dsNumber\">0xff</span>;"));
		assert_eq!(parser_cpp.highlight(&String::from("int value = 0b10;")), String::from("<span class=\"dsType\">int</span> value = <span class=\"dsNumber\">0b10</span>;"));
		assert_eq!(parser_cpp.highlight(&String::from("return 0;")), String::from("<span class=\"dsKeyword\">return</span> <span class=\"dsNumber\">0</span>;"));
	}
	
	///Test the limit of the highlighter
	#[test]
	fn test_limit_cpp_highlighter(){
		let parser_cpp: PHighlighter = load_node_highlight(&PathBuf::from("tests/parser/cpp.toml"));
		//Here we check if we do not have a token but it starts as a token, we do not treat it as a token
		assert_eq!(parser_cpp.highlight(&String::from("int_function_call();")), String::from("int_function_call();"));
	}
	
	///Test the limit of the toml highlighter (bug with an attribute which starts as a token)
	#[test]
	fn test_limit_toml_highlighter(){
		let parser_toml: PHighlighter = load_node_highlight(&PathBuf::from("tests/parser/toml.toml"));
		//Easy case which works
		assert_eq!(parser_toml.highlight(&String::from("value_something = false\n")), String::from("<span class=\"cppstandardF\">value_something</span> <span class=\"dsKeyword\">=</span> <span class=\"cppqtMacro\">false</span>\n"));
		//Of course, we don't want that but this :
		assert_eq!(parser_toml.highlight(&String::from("in_something = false\n")), String::from("<span class=\"cppstandardF\">in_something</span> <span class=\"dsKeyword\">=</span> <span class=\"cppqtMacro\">false</span>\n"));
	}
	///Test the Rust highlighter
	#[test]
	fn test_rust_highlighter(){
		let parser_cpp: PHighlighter = load_node_highlight(&PathBuf::from("tests/parser/rust.toml"));
		//Test a sequence which ends with a token !
		assert_eq!(parser_cpp.highlight(&String::from("some_macro!(shadok);\n")), String::from("<span class=\"rustBuiltinFunction\">some_macro!</span>(shadok)<span class=\"rustBuiltinFunction\">;</span>\n"));
		//Test a sequence which ends with a keyword ::
		assert_eq!(parser_cpp.highlight(&String::from("Shadok::new();\n")), String::from("<span class=\"rustBuiltinFunction\">Shadok::</span><span class=\"rustStandardFunction\">new</span>()<span class=\"rustBuiltinFunction\">;</span>\n"));
	}
}