use crate::{
ebi_framework::{
ebi_command::{EBI_COMMANDS, EbiCommand},
ebi_file_handler::{EBI_FILE_HANDLERS, EbiFileHandler},
ebi_importer_parameters,
ebi_input::{self, EbiInputType},
ebi_output::{EbiExporter, EbiOutput, EbiOutputType},
},
javascript::javascript_generator_html::{
javascript_function_name, javascript_html_form, javascript_html_header,
},
multiple_reader::MultipleReader,
python::python::pm4py_function_name,
text::HTMLEscaper,
};
use clap::Command;
use ebi_optimisation::anyhow::Result;
use inflector::Inflector;
use itertools::Itertools;
use regex::{Captures, Regex};
use std::{
collections::BTreeSet,
fs::{self, File},
io::Write,
};
pub const COMMANDS_PAGE: &'static str = "https://leemans.ch/ebi/commands.php";
pub const FILE_HANDLERS_PAGE: &'static str = "https://leemans.ch/ebi/file_handlers.php";
pub fn page_start(f: &mut Vec<u8>, headers: &str) -> Result<()> {
writeln!(
f,
"<!DOCTYPE html>
<html>
<head>
<title>Ebi - a stochastic process mining tool</title>
<link rel=\"shortcut icon\" href=\"https://bpm.rwth-aachen.de/favicon.png\">
<style>
{styles}
</style>
<script>
function myFunction0() {{
var x = document.getElementById(\"menu0\");
if (x.className === \"menu0\") {{
x.className += \" responsive\";
}} else {{
x.className = \"menu0\";
}}
}}
</script>
{headers}
</head>
<body>",
styles = fs::read_to_string("documentation/stijlen.css").unwrap(),
)?;
Ok(())
}
fn page_end(f: &mut Vec<u8>) -> Result<()> {
writeln!(
f,
"</div></body>
</html>"
)?;
Ok(())
}
pub fn documentation_filehandlers() -> Result<EbiOutput> {
let mut f = vec![];
page_start(&mut f, "")?;
menu_0(&mut f)?;
file_handlers(&mut f)?;
page_end(&mut f)?;
Ok(EbiOutput::String(String::from_utf8(f).unwrap()))
}
pub fn documentation_commands() -> Result<EbiOutput> {
let mut f = vec![];
page_start(&mut f, &javascript_html_header())?;
menu_0(&mut f)?;
commands(&mut f)?;
page_end(&mut f)?;
Ok(EbiOutput::String(String::from_utf8(f).unwrap()))
}
fn menu_0(f: &mut Vec<u8>) -> Result<()> {
writeln!(f, "<div class=\"menu0\" id =\"menu0\">")?;
writeln!(f, "<a href=\"index.php\">Ebi</a>")?;
writeln!(f, "<a href=\"{}\">Commands</a>", COMMANDS_PAGE)?;
writeln!(f, "<a href=\"{}\">Files</a>", FILE_HANDLERS_PAGE)?;
writeln!(
f,
"<a href=\"https://github.com/BPM-Research-Group/Ebi\">Github</a>"
)?;
writeln!(f, "<a href=\"https://crates.io/crates/ebi\">crates.io</a>")?;
writeln!(f, "<a href=\"https://pypi.org/project/ebi-pm/\">PyPI</a>")?;
writeln!(
f,
"<a href=\"https://www.npmjs.com/package/ebi_pm\">NPM</a>"
)?;
writeln!(
f,
"<a href=\"javascript:void(0);\" class=\"expand\" onclick=\"myFunction0()\">...</a>"
)?;
writeln!(f, "</div>")?;
writeln!(f, "<div class=\"content\">")?;
Ok(())
}
fn file_handlers(f: &mut Vec<u8>) -> Result<()> {
let file_handlers = EBI_FILE_HANDLERS.iter().collect::<BTreeSet<_>>();
for file_handler in file_handlers {
writeln!(
f,
"<a href=\"#{}\" class=\"selectable\">{} (.{})</a>",
file_handler.file_extension,
file_handler.name.to_sentence_case(),
file_handler.file_extension
)?;
writeln!(
f,
"<div class=\"selectable filehandler\" id=\"{}\">",
file_handler.file_extension
)?;
writeln!(
f,
"<h1>{} (.{})</h1>",
file_handler.name.to_sentence_case(),
file_handler.file_extension
)?;
if file_handler.get_applicable_commands().is_empty() {
writeln!(
f,
"<div>{} {} file cannot serve as input to any command.</div>",
file_handler.article.to_sentence_case(),
file_handler.name
)?;
} else {
writeln!(
f,
"<div>{} {} file can serve as input to the following commands: <ul><li>{}</li></ul></div>",
file_handler.article.to_sentence_case(),
file_handler.name,
file_handler
.get_applicable_commands()
.iter()
.map(|path| format!(
"<a href=\"{}#{}\">{}</a>",
COMMANDS_PAGE,
EbiCommand::path_to_short_string(path),
EbiCommand::path_to_string(path)
))
.join("</li><li>")
)?;
}
if file_handler.get_producing_commands().is_empty() {
writeln!(
f,
"<div>{} {} file cannot be the output of any command.</div>",
file_handler.article.to_sentence_case(),
file_handler.name
)?;
} else {
writeln!(
f,
"<div>{} {} file can be the output of the following commands: <ul><li>{}</li></ul></div>",
file_handler.article.to_sentence_case(),
file_handler.name,
file_handler
.get_producing_commands()
.iter()
.map(|path| format!(
"<a href=\"{}#{}\">{}</a>",
COMMANDS_PAGE,
EbiCommand::path_to_short_string(path),
EbiCommand::path_to_string(path)
))
.join("</li><li>")
)?;
}
file_handler_importer_parameters(f, file_handler)?;
let regex = Regex::new("\\\\lstinputlisting\\[[^\\]]*\\]\\{([^\\}]*)\\}").unwrap();
writeln!(
f,
"<div>File format specification: {}</div>",
regex.replace_all(
&file_handler.format_specification.latex_to_html_string(),
|caps: &Captures| {
let file_name = &caps[1].replacen(".", "", 1);
{
let mut reader =
MultipleReader::from_file(File::open(file_name).expect(&format!(
"Something went wrong with reading {}.",
file_name
)));
if let Some(importer) = &file_handler.object_importers.iter().next() {
(importer.get_importer())(
&mut reader.get().unwrap(),
&importer.default_parameter_values(),
)
.unwrap();
} else if let Some(importer) =
&file_handler.object_importers_fallible.iter().next()
{
(importer.get_importer())(
&mut reader.get().unwrap(),
&importer.default_parameter_values(),
)
.unwrap();
} else {
}
}
format!(
"<pre><code>{}</code></pre>",
fs::read_to_string(file_name)
.unwrap()
.escape_html()
.replace("\n", "</code>\n<code>")
)
}
)
)?;
writeln!(f, "</div>")?;
}
Ok(())
}
fn file_handler_importer_parameters(f: &mut Vec<u8>, file_handler: &EbiFileHandler) -> Result<()> {
Ok({
let importer_parameters =
ebi_importer_parameters::file_handler_2_importer_parameters(file_handler);
if !importer_parameters.is_empty() {
writeln!(
f,
"<div>On importing .{} files, the following optional importer parameters may be given, where X is the rank of the input in the command:<br><table>",
file_handler.file_extension
)?;
for parameter in importer_parameters {
writeln!(
f,
"<tr><td><span class=\"texttt\">--inputX_{}</span></td><td>{}<br>{}",
parameter.name(),
parameter.explanation().escape_html(),
ebi_importer_parameters::explanation_with_values(parameter).escape_html()
)?;
if ebi_importer_parameters::has_accepted_values(parameter) {
writeln!(
f,
"<br>Accepted values: {}.",
ebi_importer_parameters::explanation_with_values(parameter).escape_html()
)?;
}
writeln!(f, "</td></tr>")?;
}
writeln!(f, "</table></div>")?;
}
})
}
fn commands(f: &mut Vec<u8>) -> Result<()> {
writeln!(f, "<div class=\"commands\">")?;
for path in EBI_COMMANDS.get_command_paths() {
if let EbiCommand::Command {
name_long,
explanation_short,
explanation_long,
latex_link,
cli_command,
exact_arithmetic,
input_types: input_typess,
input_names,
input_helps,
output_type,
..
} = path.last().unwrap()
&& input_typess.len() > 0
{
writeln!(
f,
"<a href=\"#{}\" class=\"selectable\">{}</a>",
EbiCommand::path_to_short_string(&path),
EbiCommand::path_to_string(&path)
)?;
writeln!(
f,
"<div class=\"selectable command\" id=\"{}\">",
EbiCommand::path_to_short_string(&path)
)?;
writeln!(f, "<h1>{}</h1>", EbiCommand::path_to_string(&path))?;
if let Some(_) = name_long {
writeln!(
f,
"<div>Short alias: {}.</div>",
EbiCommand::path_to_short_string(&path)
)?;
}
{
writeln!(
f,
"<div>{}",
if let Some(l) = explanation_long {
l
} else {
explanation_short
}
)?;
if latex_link.is_some() {
writeln!(
f,
" More information is available in the <a href=\"https://git.rwth-aachen.de/rwth-bpm/rustlibrary/-/raw/main/build/nightly/manual.pdf?ref_type=heads&inline=true\">PDF manual</a>."
)?;
}
writeln!(f, "</div>")?;
}
if path.last().unwrap().is_in_javascript() {
writeln!(f, "<h2>Run online</h2>")?;
writeln!(
f,
"This command can be run here, in the browser.
All files remain on your computer, and are not uploaded.
There is a limit of 4GB of RAM.
<p>"
)?;
javascript_html_form(f, &path)?;
}
writeln!(f, "<h2>Parameters</h2><table>")?;
command_standard_parameters(f, input_typess, input_names, input_helps)?;
command_custom_parameters(f, cli_command)?;
command_output_type(f, output_type)?;
command_output(f, output_type)?;
command_exact_arithmetic(f, *exact_arithmetic)?;
writeln!(f, "</table>")?;
command_availability(f, &path, input_names)?;
writeln!(f, "</div>")?;
}
}
writeln!(f, "</div>")?;
Ok(())
}
fn command_availability(
f: &mut Vec<u8>,
path: &Vec<&EbiCommand>,
input_names: &[&str],
) -> Result<()> {
writeln!(f, "<h2>Availability</h2><table>")?;
writeln!(
f,
"<tr><td>Command-line interface</td><td>available, with all parameters mentioned above</td></tr>"
)?;
writeln!(
f,
"<tr><td>PM4Py/Python/PyPI</td><td>{}</td></tr>",
if path.last().unwrap().is_in_python() {
format!(
"function <span class=\"texttt\">{}</span>({})",
pm4py_function_name(&path).escape_html(),
input_names
.iter()
.map(|s| format!("<{s}>"))
.join(", ")
)
} else {
"not available".to_string()
}
)?;
writeln!(
f,
"<tr><td>Javascript/online/NPM</td><td>{}</td></tr>",
if path.last().unwrap().is_in_javascript() {
format!(
"function <span class=\"texttt\">{}</span>({}, output_file_extension)",
javascript_function_name(&path).escape_html(),
input_names
.iter()
.map(|s| format!("<{s}> as a string"))
.join(", ")
)
} else {
"not available".to_string()
}
)?;
writeln!(
f,
"<tr><td>Java</td><td>{}</td></tr>",
if path.last().unwrap().is_in_java() {
format!(
"function <span class=\"texttt\">call_ebi_internal</span>(\"{}\", String output_format, String[] inputs);",
EbiCommand::path_to_string(&path)
)
} else {
"not available".to_string()
}
)?;
writeln!(f, "</table>")?;
Ok(())
}
fn command_standard_parameters(
f: &mut Vec<u8>,
input_types: &'static [&'static [&'static EbiInputType]],
input_names: &'static [&'static str],
input_helps: &'static [&'static str],
) -> Result<()> {
for (input_name, (input_types, input_help)) in input_names
.iter()
.zip(input_types.iter().zip(input_helps.iter()))
{
writeln!(f, "<tr>")?;
writeln!(f, "<td><{}></td>", input_name.escape_html())?;
writeln!(f, "<td>")?;
writeln!(f, "{}.", input_help.to_sentence_case())?;
writeln!(
f,
"<br>Accepted values:<ul><li>{}</li></ul>",
EbiInputType::get_possible_inputs_with_html(input_types).join("</li><li>")
)?;
if !ebi_importer_parameters::merge_importer_parameters(input_types).is_empty() {
writeln!(
f,
"For some of these files, importer parameters are available.<br>"
)?;
}
if let Some(default) = ebi_input::default(input_types) {
writeln!(
f,
"Mandatory: no. If no value is provided, a default of {} will be used. It can also be provided on STDIN by giving a `-' on the command line.",
default
)?;
} else {
writeln!(
f,
"Mandatory: yes, though it can be given on STDIN by giving a `-' on the command line."
)?;
}
writeln!(f, "</td></tr>")?;
}
Ok(())
}
fn command_custom_parameters(
f: &mut Vec<u8>,
cli_command: &Option<fn(command: Command) -> Command>,
) -> Result<()> {
if let Some(fu) = cli_command {
for arg in (fu)(Command::new("")).get_arguments() {
writeln!(f, "<tr>")?;
writeln!(
f,
"<td>{}</td>",
if let Some(short) = arg.get_short() {
if let Some(long) = arg.get_long() {
format!(
"<span class=\"parameter\">-{}</span> or <span class=\"parameter\">--{}</span>",
short, long
)
} else {
format!("<span class=\"parameter\">-{}</span>", short.to_string())
}
} else {
if let Some(long) = arg.get_long() {
format!("<span class=\"parameter\">--{}</span>", long)
} else {
format!("<{}>", arg.get_value_names().unwrap()[0])
}
}
)?;
writeln!(
f,
"<td>{}",
if let Some(h) = arg.get_long_help() {
h.to_string()
} else {
arg.get_help().unwrap().to_string()
}
)?;
if arg.get_short().is_none() && arg.get_long().is_none() {
if let Some(default) = arg.get_default_values().iter().next() {
writeln!(
f,
"<br>Mandatory: {}.",
if arg.is_required_set() {
"yes".to_owned()
} else {
format!(
"no; if not provided, a default value of {} will be used",
default.to_string_lossy()
)
}
)?;
} else {
writeln!(
f,
"<br>Mandatory: {}.",
if arg.is_required_set() { "yes" } else { "no" }
)?;
}
} else {
writeln!(f, "<br>Mandatory: no.")?;
}
writeln!(f, "</td></tr>")?;
}
}
Ok(())
}
fn command_output_type(f: &mut Vec<u8>, output_type: &EbiOutputType) -> Result<()> {
if output_type.get_exporters().len() > 1 {
let output_extensions = output_type
.get_exporters()
.iter()
.collect::<BTreeSet<_>>()
.iter()
.map(|exporter| {
format!(
"<a href=\"{}#{}\" class=\"parameter\">{}</a> ({})",
FILE_HANDLERS_PAGE,
exporter.get_extension(),
exporter.get_extension(),
exporter.get_name()
)
})
.collect::<Vec<_>>()
.join("</li><li>");
writeln!(
f,
"<tr><td><span class=\"parameter\">-t</span> or<br><span class=\"parameter\">--output_type</span> <OUTPUT_TYPE></td>"
)?;
writeln!(
f,
"<td>The file format as which Ebi should write the result. To be given as the file extension without period. Possible values are: <ul><li>{}</li></ul>",
output_extensions
)?;
writeln!(
f,
"Mandatory: no. If no value is provided, the default of <span class=\"parameter\">{}</span> ({}) will be used.",
output_type.get_default_exporter().get_extension(),
output_type.get_default_exporter().get_name()
)?;
writeln!(f, "</td></tr>")?;
}
Ok(())
}
fn command_output(f: &mut Vec<u8>, output_type: &EbiOutputType) -> Result<()> {
writeln!(
f,
"<tr><td><span class=\"parameter\">-o</span> or<br> <span class=\"parameter\">--output</span> <FILE></td>"
)?;
if output_type.get_exporters().len() == 1 {
let exporter = output_type.get_exporters().remove(0);
writeln!(
f,
"<td>The {} file to which the result must be written.<br>",
exporter
)?;
} else {
writeln!(
f,
"<td>The file to which the results must be written. Ebi will use the file extension to determine the output file format, which may be <ul><li>{}</li></ul>",
output_types(output_type)
)?;
}
let default = if output_type.get_exporters().len() > 1 {
format!(
"in the file format specified by <span class=\"parameter\">-t</span>, which by default is {} {} (<a href=\"{}#{}\">.{}</a>)",
output_type.get_default_exporter().get_extension(),
output_type.get_default_exporter().get_name(),
FILE_HANDLERS_PAGE,
output_type.get_default_exporter().get_extension(),
output_type.get_default_exporter().get_extension()
)
} else {
format!(
"in the file format of {} (<a href=\"{}#{}\">.{}</a>)",
output_type.get_default_exporter().get_name(),
FILE_HANDLERS_PAGE,
output_type.get_default_exporter().get_extension(),
output_type.get_default_exporter().get_extension()
)
};
writeln!(
f,
"Mandatory: no. If no value is provided, the result will be written to STDOUT {}.",
default
)?;
writeln!(f, "<td></tr>")?;
Ok(())
}
pub fn output_types(output_type: &EbiOutputType) -> String {
let mut list = output_type
.get_exporters()
.into_iter()
.collect::<BTreeSet<_>>()
.iter()
.map(|exp| match exp {
EbiExporter::Object(_, file_handler) => format!(
"{} (<a href=\"{}#{}\">.{}</a>)",
file_handler.name,
FILE_HANDLERS_PAGE,
file_handler.file_extension,
file_handler.file_extension
),
_ => exp.to_string(),
})
.collect::<Vec<_>>();
if list.len() == 1 {
return list.remove(0);
}
list.join("</li><li>")
}
fn command_exact_arithmetic(f: &mut Vec<u8>, exact_arithmetic: bool) -> Result<()> {
if exact_arithmetic {
writeln!(
f,
"<tr>
<td><span class=\"parameter\">-a</span> or <span class=\"parameter\">--approximate</span></td>
<td>Use approximate arithmetic instead of exact arithmetic.
<br>Mandatory: no. If not provided, exact arithmetic will be used.</td></tr>"
)?;
}
Ok(())
}
pub fn documentation_home() -> Result<EbiOutput> {
let mut f = vec![];
page_start(&mut f, "")?;
menu_0(&mut f)?;
writeln!(
f,
"{}",
fs::read_to_string("README.md").unwrap().md_to_html_string()
)?;
page_end(&mut f)?;
Ok(EbiOutput::String(String::from_utf8(f).unwrap()))
}