#![forbid(unsafe_code)]
use pulldown_cmark::{html, BrokenLink, Options, Parser};
use std::env;
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;
fn dry_run(text: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
let p = Parser::new_with_broken_link_callback(
text,
opts,
Some(|link: BrokenLink<'_>| {
broken_links.push(link.into_static());
None
}),
);
let count = p.count();
println!("{} events", count);
}
fn print_events(text: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
let parser = Parser::new_with_broken_link_callback(
text,
opts,
Some(|link: BrokenLink<'_>| {
broken_links.push(link.into_static());
None
}),
)
.into_offset_iter();
for (event, range) in parser {
println!("{:?}: {:?}", range, event);
}
println!("EOF");
}
fn brief(program: &str) -> String {
format!(
"Usage: {} [options]\n\n{}",
program, "Reads markdown from file or standard input and emits HTML.",
)
}
pub fn main() -> std::io::Result<()> {
let args: Vec<_> = env::args().collect();
let mut opts = getopts::Options::new();
opts.optflag("h", "help", "this help message");
opts.optflag("d", "dry-run", "dry run, produce no output");
opts.optflag("e", "events", "print event sequence instead of rendering");
opts.optflag("T", "enable-tables", "enable GitHub-style tables");
opts.optflag("m", "enable-math", "enable LaTeX-style math");
opts.optflag("F", "enable-footnotes", "enable GitHub-style footnotes");
opts.optflag(
"f",
"enable-old-footnotes",
"enable Hoedown-style footnotes",
);
opts.optflag(
"S",
"enable-strikethrough",
"enable GitHub-style strikethrough",
);
opts.optflag("U", "enable-superscript", "enable superscript");
opts.optflag("B", "enable-subscript", "enable subscript");
opts.optflag("L", "enable-tasklists", "enable GitHub-style task lists");
opts.optflag("P", "enable-smart-punctuation", "enable smart punctuation");
opts.optflag(
"H",
"enable-heading-attributes",
"enable heading attributes",
);
opts.optflag("M", "enable-metadata-blocks", "enable metadata blocks");
opts.optflag(
"R",
"reject-broken-links",
"fail if input file has broken links",
);
opts.optflag("G", "enable-gfm", "enable misc GFM features");
opts.optflag(
"D",
"enable-definition-list",
"enable Commonmark-HS-Extensions compatible definition lists",
);
opts.optflag("W", "enable-wikilinks", "enable wikilinks");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
eprintln!("{}\n{}", f, opts.usage(&brief(&args[0])));
std::process::exit(1);
}
};
if matches.opt_present("help") {
println!("{}", opts.usage(&brief(&args[0])));
return Ok(());
}
let mut opts = Options::empty();
if matches.opt_present("enable-tables") {
opts.insert(Options::ENABLE_TABLES);
}
if matches.opt_present("enable-math") {
opts.insert(Options::ENABLE_MATH);
}
if matches.opt_present("enable-footnotes") {
opts.insert(Options::ENABLE_FOOTNOTES);
}
if matches.opt_present("enable-old-footnotes") {
opts.insert(Options::ENABLE_OLD_FOOTNOTES);
}
if matches.opt_present("enable-strikethrough") {
opts.insert(Options::ENABLE_STRIKETHROUGH);
}
if matches.opt_present("enable-superscript") {
opts.insert(Options::ENABLE_SUPERSCRIPT);
}
if matches.opt_present("enable-subscript") {
opts.insert(Options::ENABLE_SUBSCRIPT);
}
if matches.opt_present("enable-tasklists") {
opts.insert(Options::ENABLE_TASKLISTS);
}
if matches.opt_present("enable-smart-punctuation") {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
}
if matches.opt_present("enable-heading-attributes") {
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
}
if matches.opt_present("enable-metadata-blocks") {
opts.insert(Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
opts.insert(Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS);
}
if matches.opt_present("enable-gfm") {
opts.insert(Options::ENABLE_GFM);
}
if matches.opt_present("enable-definition-list") {
opts.insert(Options::ENABLE_DEFINITION_LIST);
}
if matches.opt_present("enable-wikilinks") {
opts.insert(Options::ENABLE_WIKILINKS);
}
let mut input = String::new();
let mut broken_links = vec![];
if !&matches.free.is_empty() {
for filename in &matches.free {
let real_path = PathBuf::from(filename);
let mut f = File::open(&real_path).expect("file not found");
f.read_to_string(&mut input)
.expect("something went wrong reading the file");
if matches.opt_present("events") {
print_events(&input, opts, &mut broken_links);
} else if matches.opt_present("dry-run") {
dry_run(&input, opts, &mut broken_links);
} else {
pulldown_cmark(&input, opts, &mut broken_links);
}
}
} else {
let _ = io::stdin().lock().read_to_string(&mut input);
if matches.opt_present("events") {
print_events(&input, opts, &mut broken_links);
} else if matches.opt_present("dry-run") {
dry_run(&input, opts, &mut broken_links);
} else {
pulldown_cmark(&input, opts, &mut broken_links);
}
}
if matches.opt_present("reject-broken-links") && !broken_links.is_empty() {
eprintln!("Error: {} broken links:", broken_links.len());
for link in broken_links {
let start = link.span.start;
let end = link.span.end;
let reference = link.reference;
eprintln!("[{start}-{end}]: {reference}");
}
std::process::exit(1);
}
Ok(())
}
pub fn pulldown_cmark(input: &str, opts: Options, broken_links: &mut Vec<BrokenLink<'static>>) {
let mut p = Parser::new_with_broken_link_callback(
input,
opts,
Some(|link: BrokenLink<'_>| {
broken_links.push(link.into_static());
None
}),
);
let stdio = io::stdout();
let buffer = std::io::BufWriter::with_capacity(1024 * 1024, stdio.lock());
let _ = html::write_html_io(buffer, &mut p);
}