// Copyright 2015 Google Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //! Command line tool to exercise pulldown-cmark. #![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>) { 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>) { 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("", "enable-old-footnotes", "enable Hoedown-style footnotes"); opts.optflag( "S", "enable-strikethrough", "enable GitHub-style strikethrough", ); 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"); 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-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); } 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>) { 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); }