use crate::ModuleMap;
use std::collections::BTreeMap;
use std::fs;
use std::io::Write;
use std::path::Path;
pub const COMMENT_PREFIX: &str = "#";
pub const FUNC_PREFIX: &str = "export.";
pub const MODULE_COMMENT_PREFIX: &str = "#!";
pub struct Function {
name: String,
comments: Vec<String>,
}
pub struct Module {
name: String,
section: String,
functions: Vec<Function>,
comments: Vec<String>,
}
impl Module {
pub fn new(ns: String) -> Self {
let parts: Vec<&str> = ns.split("::").collect();
let module_name = parts[parts.len() - 1];
let section_name = parts[parts.len() - 2];
Module {
name: String::from(module_name),
section: String::from(section_name),
functions: Vec::new(),
comments: Vec::new(),
}
}
pub fn markdown_file_name(&self) -> String {
format!("{}_{}.md", self.name, self.section)
}
}
pub struct Stdlib {
modules: BTreeMap<String, Module>,
}
impl Stdlib {
pub fn new() -> Self {
Stdlib {
modules: BTreeMap::new(),
}
}
}
trait Renderer {
fn render(stdlib: &Stdlib, output_dir: &str);
}
struct MarkdownRenderer {}
impl Renderer for MarkdownRenderer {
fn render(stdlib: &Stdlib, output_dir: &str) {
for (ns, module) in &stdlib.modules {
let file_name = module.markdown_file_name();
let file_path = Path::new(output_dir).join(file_name);
let mut f = fs::OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(file_path)
.expect("unable to open stdlib markdown file");
f.write_all(module.comments.join("\n").as_bytes())
.expect("unable to write module comments");
let header = format!(
"\n## {}\n| Procedure | Description |\n| ----------- | ------------- |\n",
ns
);
f.write_all(header.as_bytes())
.expect("unable to write header to writer");
for func in module.functions.iter() {
let func_output = format!(
"| {} | {} |\n",
func.name,
func.comments.join("<br />").replace('|', "\\|")
);
f.write_all(func_output.as_bytes())
.expect("unable to write func to writer");
}
}
}
}
#[derive(PartialEq)]
enum AsmSourceState {
Empty,
Comment,
Func,
ModuleComment,
}
pub fn build_stdlib_docs(module_map: &ModuleMap, doc_functions_path: &str) {
let mut stdlib = Stdlib::new();
for (ns, source) in module_map {
parse_module(ns.clone(), source.clone(), &mut stdlib);
}
render_docs(&stdlib, doc_functions_path);
}
fn render_docs(stdlib: &Stdlib, output_dir: &str) {
fs::remove_dir_all(output_dir).unwrap();
fs::create_dir(output_dir).unwrap();
MarkdownRenderer::render(stdlib, output_dir);
}
fn parse_module(ns: String, source: String, stdlib: &mut Stdlib) {
let current_state = AsmSourceState::Empty;
let mut comments = Vec::<String>::new();
let module = stdlib
.modules
.entry(ns.clone())
.or_insert_with(|| Module::new(ns.clone()));
for line in source.lines() {
let new_state = parse_new_state(line);
if new_state != current_state {
match new_state {
AsmSourceState::Func => {
let func_name = remove_prefix(FUNC_PREFIX, line);
module.functions.push(Function {
name: func_name,
comments: comments.clone(),
});
comments.clear();
}
AsmSourceState::Comment => comments.push(remove_prefix(COMMENT_PREFIX, line)),
AsmSourceState::Empty => comments.clear(),
AsmSourceState::ModuleComment => module
.comments
.push(remove_prefix(MODULE_COMMENT_PREFIX, line)),
}
} else {
match new_state {
AsmSourceState::Comment => comments.push(remove_prefix(COMMENT_PREFIX, line)),
AsmSourceState::Empty => comments.clear(),
_ => (),
}
}
}
}
fn parse_new_state(line: &str) -> AsmSourceState {
if line.starts_with(MODULE_COMMENT_PREFIX) {
AsmSourceState::ModuleComment
} else if line.starts_with(COMMENT_PREFIX) {
AsmSourceState::Comment
} else if line.starts_with(FUNC_PREFIX) {
AsmSourceState::Func
} else {
AsmSourceState::Empty
}
}
fn remove_prefix(prefix: &str, line: &str) -> String {
String::from(line.strip_prefix(prefix).unwrap())
}