forked from tforgione/md2pdf
		
	Better binary, adds support for links
This commit is contained in:
		
							parent
							
								
									9955be2107
								
							
						
					
					
						commit
						c8931b47f4
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -371,6 +371,7 @@ dependencies = [ | ||||
| name = "md2pdf" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "pulldown-cmark 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "tectonic 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| name = "md2pdf" | ||||
| version = "0.1.0" | ||||
| authors = ["Thomas Forgione <thomas@forgione.fr>"] | ||||
| description = "A small utility to convert markdown files to pdf exploiting tectonic." | ||||
| edition = "2018" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| @ -9,3 +10,4 @@ edition = "2018" | ||||
| [dependencies] | ||||
| tectonic = "0.1.11" | ||||
| pulldown-cmark = "0.5.2" | ||||
| clap = "2.33.0" | ||||
|  | ||||
							
								
								
									
										130
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| use pulldown_cmark::{Event, Parser, Tag}; | ||||
| 
 | ||||
| pub const LATEX_HEADER:&str = r#"\documentclass{scrartcl}
 | ||||
| \usepackage{graphicx} | ||||
| \usepackage{hyperref} | ||||
| \usepackage{listings} | ||||
| \usepackage{xcolor} | ||||
| \definecolor{colKeys}{rgb}{0,0.5,0} | ||||
| \definecolor{colIdentifier}{rgb}{0,0,0} | ||||
| \definecolor{colComments}{rgb}{0,0.5,1} | ||||
| \definecolor{colString}{rgb}{0.6,0.1,0.1} | ||||
| \definecolor{colBackground}{rgb}{0.95,0.95,1} | ||||
| \lstset{%configuration de listings | ||||
|    float=hbp,% | ||||
|    basicstyle=\ttfamily\small,% | ||||
|    % | ||||
|    identifierstyle=\color{colIdentifier}, % | ||||
|    keywordstyle=\color{colKeys}, % | ||||
|    stringstyle=\color{colString}, % | ||||
|    commentstyle=\color{colComments}\textit, % | ||||
|    % | ||||
|    backgroundcolor=\color{colBackground},% | ||||
|    % | ||||
|    columns=flexible, % | ||||
|    tabsize=2, % | ||||
|    frame=trbl, % | ||||
|    %frameround=tttt,% | ||||
|    extendedchars=true, % | ||||
|    showspaces=false, % | ||||
|    showstringspaces=false, % | ||||
|    numbers=left, % | ||||
|    numberstyle=\tiny, % | ||||
|    breaklines=true, % | ||||
|    breakautoindent=true, % | ||||
|    captionpos=b,% | ||||
|    xrightmargin=0.2cm, % | ||||
|    xleftmargin=0.2cm | ||||
| } | ||||
| \begin{document} | ||||
| "#;
 | ||||
| 
 | ||||
| pub const LATEX_FOOTER: &str = "\n\\end{document}\n"; | ||||
| 
 | ||||
| pub fn markdown_to_latex(markdown: String) -> String { | ||||
|     let mut output = String::from(LATEX_HEADER); | ||||
| 
 | ||||
|     let parser = Parser::new(&markdown); | ||||
| 
 | ||||
|     for event in parser { | ||||
|         match event { | ||||
|             Event::Start(Tag::Header(level)) => { | ||||
|                 output.push_str("\\"); | ||||
|                 for _ in 1 .. level { | ||||
|                     output.push_str("sub"); | ||||
|                 } | ||||
|                 output.push_str("section{"); | ||||
|             }, | ||||
|             Event::End(Tag::Header(_)) => output.push_str("}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::Emphasis) => output.push_str("\\emph{"), | ||||
|             Event::End(Tag::Emphasis) => output.push_str("}"), | ||||
| 
 | ||||
|             Event::Start(Tag::Strong) => output.push_str("\\textbf{"), | ||||
|             Event::End(Tag::Strong) => output.push_str("}"), | ||||
| 
 | ||||
|             Event::Start(Tag::List(None)) => output.push_str("\\begin{itemize}\n"), | ||||
|             Event::End(Tag::List(None)) => output.push_str("\\end{itemize}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::List(Some(_))) => output.push_str("\\begin{enumerate}\n"), | ||||
|             Event::End(Tag::List(Some(_))) => output.push_str("\\end{enumerate}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::Link(_, url, _)) => { | ||||
|                 output.push_str("\\href{"); | ||||
|                 output.push_str(&*url); | ||||
|                 output.push_str("}{"); | ||||
|             }, | ||||
| 
 | ||||
|             Event::End(Tag::Link(_, _, _)) => { | ||||
|                 output.push_str("}"); | ||||
|             }, | ||||
| 
 | ||||
|             Event::Start(Tag::Image(_, path, title)) => { | ||||
|                 output.push_str("\\begin{figure}\n"); | ||||
|                 output.push_str("\\centering\n"); | ||||
|                 output.push_str("\\includegraphics[width=\\textwidth]{");; | ||||
|                 output.push_str(&*path); | ||||
|                 output.push_str("}\n"); | ||||
|                 output.push_str("\\caption{"); | ||||
|                 output.push_str(&*title); | ||||
|                 output.push_str("}\n\\end{figure}\n"); | ||||
|             }, | ||||
| 
 | ||||
|             Event::Start(Tag::Item) => output.push_str("\\item "), | ||||
|             Event::End(Tag::Item) => output.push_str("\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::CodeBlock(lang)) => { | ||||
|                 if ! lang.is_empty() { | ||||
|                     output.push_str("\\begin{lstlisting}[language="); | ||||
|                     output.push_str(&*lang); | ||||
|                     output.push_str("]\n"); | ||||
|                 } else { | ||||
|                     output.push_str("\\begin{lstlisting}\n"); | ||||
|                 } | ||||
|             }, | ||||
| 
 | ||||
|             Event::End(Tag::CodeBlock(_)) => { | ||||
|                 output.push_str("\n\\end{lstlisting}\n"); | ||||
|             }, | ||||
| 
 | ||||
|             Event::Text(t) => { | ||||
|                 output.push_str(&*t); | ||||
|             }, | ||||
| 
 | ||||
|             Event::SoftBreak => { | ||||
|                 output.push('\n'); | ||||
|             }, | ||||
| 
 | ||||
|             _ => (), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     output.push_str(LATEX_FOOTER); | ||||
| 
 | ||||
|     output | ||||
| } | ||||
| 
 | ||||
| pub fn markdown_to_pdf(markdown: String) -> Result<Vec<u8>, tectonic::Error> { | ||||
|     tectonic::latex_to_pdf(markdown_to_latex(markdown)) | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										149
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,115 +1,60 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{Read, Write}; | ||||
| use std::process::exit; | ||||
| 
 | ||||
| use pulldown_cmark::{Event, Parser, Tag}; | ||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||
| 
 | ||||
| use md2pdf::{markdown_to_latex, markdown_to_pdf}; | ||||
| 
 | ||||
| macro_rules! unwrap { | ||||
|     ($e: expr, $m: expr) => { | ||||
|         match $e { | ||||
|             Ok(v) => v, | ||||
|             Err(e) => { | ||||
|                 eprintln!("{}: {}", $m, e); | ||||
|                 exit(1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
| 
 | ||||
|     let mut markdown_input = String::new(); | ||||
|     let mut input = File::open("markdown.md").unwrap(); | ||||
|     input.read_to_string(&mut markdown_input).unwrap(); | ||||
|     let matches = App::new(crate_name!()) | ||||
|         .bin_name(crate_name!()) | ||||
|         .version(crate_version!()) | ||||
|         .author(crate_authors!("\n")) | ||||
|         .about(crate_description!()) | ||||
|         .arg(Arg::with_name("INPUT") | ||||
|              .long("input") | ||||
|              .short("i") | ||||
|              .help("Input markdown files") | ||||
|              .required(true) | ||||
|              .takes_value(true)) | ||||
|         .arg(Arg::with_name("OUTPUT") | ||||
|              .long("output") | ||||
|              .short("o") | ||||
|              .help("Output tex or pdf file") | ||||
|              .required(true) | ||||
|              .takes_value(true)) | ||||
|         .get_matches(); | ||||
| 
 | ||||
|     let parser = Parser::new(&markdown_input); | ||||
|     let mut content = String::new(); | ||||
|     let mut input = unwrap!(File::open(matches.value_of("INPUT").unwrap()), "couldn't open input file"); | ||||
|     unwrap!(input.read_to_string(&mut content), "couldn't read file content"); | ||||
| 
 | ||||
|     let mut tex_output = String::new(); | ||||
|     tex_output.push_str("\\documentclass{scrartcl}\n"); | ||||
|     tex_output.push_str("\\usepackage{graphicx}\n"); | ||||
|     tex_output.push_str("\\usepackage{listings}\n"); | ||||
|     tex_output.push_str("\\usepackage{xcolor}\n"); | ||||
|     tex_output.push_str("\\definecolor{colKeys}{rgb}{0,0.5,0}\n"); | ||||
|     tex_output.push_str("\\definecolor{colIdentifier}{rgb}{0,0,0}\n"); | ||||
|     tex_output.push_str("\\definecolor{colComments}{rgb}{0,0.5,1}\n"); | ||||
|     tex_output.push_str("\\definecolor{colString}{rgb}{0.6,0.1,0.1}\n"); | ||||
|     tex_output.push_str("\\definecolor{colBackground}{rgb}{0.95,0.95,1}\n"); | ||||
|     tex_output.push_str("\\lstset{%configuration de listings\n"); | ||||
|     tex_output.push_str("    float=hbp,%\n"); | ||||
|     tex_output.push_str("    basicstyle=\\ttfamily\\small,%\n"); | ||||
|     tex_output.push_str("    %\n"); | ||||
|     tex_output.push_str("    identifierstyle=\\color{colIdentifier}, %\n"); | ||||
|     tex_output.push_str("    keywordstyle=\\color{colKeys}, %\n"); | ||||
|     tex_output.push_str("    stringstyle=\\color{colString}, %\n"); | ||||
|     tex_output.push_str("    commentstyle=\\color{colComments}\textit, %\n"); | ||||
|     tex_output.push_str("    %\n"); | ||||
|     tex_output.push_str("    backgroundcolor=\\color{colBackground},%\n"); | ||||
|     tex_output.push_str("    %\n"); | ||||
|     tex_output.push_str("    columns=flexible, %\n"); | ||||
|     tex_output.push_str("    tabsize=2, %\n"); | ||||
|     tex_output.push_str("    frame=trbl, %\n"); | ||||
|     tex_output.push_str("    %frameround=tttt,%\n"); | ||||
|     tex_output.push_str("    extendedchars=true, %\n"); | ||||
|     tex_output.push_str("    showspaces=false, %\n"); | ||||
|     tex_output.push_str("    showstringspaces=false, %\n"); | ||||
|     tex_output.push_str("    numbers=left, %\n"); | ||||
|     tex_output.push_str("    numberstyle=\\tiny, %\n"); | ||||
|     tex_output.push_str("    breaklines=true, %\n"); | ||||
|     tex_output.push_str("    breakautoindent=true, %\n"); | ||||
|     tex_output.push_str("    captionpos=b,%\n"); | ||||
|     tex_output.push_str("    xrightmargin=0.2cm, %\n"); | ||||
|     tex_output.push_str("    xleftmargin=0.2cm\n"); | ||||
|     tex_output.push_str("}\n"); | ||||
|     tex_output.push_str("\\begin{document}\n"); | ||||
|     let output_path = matches.value_of("OUTPUT").unwrap(); | ||||
|     let mut output = unwrap!(File::create(output_path), "couldn't open output file"); | ||||
| 
 | ||||
|     for event in parser { | ||||
|         match event { | ||||
|             Event::Start(Tag::Header(level)) => { | ||||
|                 tex_output.push_str("\\"); | ||||
|                 for _ in 1 .. level { | ||||
|                     tex_output.push_str("sub"); | ||||
|                 } | ||||
|                 tex_output.push_str("section{"); | ||||
|             }, | ||||
|             Event::End(Tag::Header(_)) => tex_output.push_str("}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::Emphasis) => tex_output.push_str("\\emph{"), | ||||
|             Event::End(Tag::Emphasis) => tex_output.push_str("}"), | ||||
| 
 | ||||
|             Event::Start(Tag::Strong) => tex_output.push_str("\\textbf{"), | ||||
|             Event::End(Tag::Strong) => tex_output.push_str("}"), | ||||
| 
 | ||||
|             Event::Start(Tag::List(None)) => tex_output.push_str("\\begin{itemize}\n"), | ||||
|             Event::End(Tag::List(None)) => tex_output.push_str("\\end{itemize}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::List(Some(_))) => tex_output.push_str("\\begin{enumerate}\n"), | ||||
|             Event::End(Tag::List(Some(_))) => tex_output.push_str("\\end{enumerate}\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::Image(_, path, title)) => { | ||||
|                 tex_output.push_str("\\begin{figure}\n"); | ||||
|                 tex_output.push_str("\\centering\n"); | ||||
|                 tex_output.push_str("\\includegraphics[width=\\textwidth]{");; | ||||
|                 tex_output.push_str(&*path); | ||||
|                 tex_output.push_str("}\n"); | ||||
|                 tex_output.push_str("\\caption{"); | ||||
|                 tex_output.push_str(&*title); | ||||
|                 tex_output.push_str("}\n\\end{figure}\n"); | ||||
|             }, | ||||
| 
 | ||||
|             Event::Start(Tag::Item) => tex_output.push_str("\\item "), | ||||
|             Event::End(Tag::Item) => tex_output.push_str("\n"), | ||||
| 
 | ||||
|             Event::Start(Tag::CodeBlock(lang)) => { | ||||
|                 if ! lang.is_empty() { | ||||
|                     tex_output.push_str("\\begin{lstlisting}[language="); | ||||
|                     tex_output.push_str(&*lang); | ||||
|                     tex_output.push_str("]\n"); | ||||
|     if output_path.ends_with(".tex") { | ||||
|         let tex = markdown_to_latex(content); | ||||
|         unwrap!(output.write(tex.as_bytes()), "couldn't write output file"); | ||||
|     } else if output_path.ends_with(".pdf") { | ||||
|         let data = unwrap!(markdown_to_pdf(content), "error while compiling latex, this is most likely a bug"); | ||||
|         unwrap!(output.write(&data), "coudln't write output file"); | ||||
|     } else { | ||||
|                     tex_output.push_str("\\begin{lstlisting}\n"); | ||||
|                 } | ||||
|             }, | ||||
| 
 | ||||
|             Event::End(Tag::CodeBlock(_)) => { | ||||
|                 tex_output.push_str("\n\\end{lstlisting}\n"); | ||||
|         eprintln!("unknown file format for output: {}", output_path); | ||||
|         exit(1); | ||||
|     } | ||||
| 
 | ||||
|             Event::Text(t) => tex_output.push_str(&*t), | ||||
|             _ => (), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     tex_output.push_str("\n\\end{document}\n"); | ||||
| 
 | ||||
|     println!("{}", tex_output); | ||||
| 
 | ||||
|     let pdf_data = tectonic::latex_to_pdf(tex_output).expect("processing failed"); | ||||
|     let mut output = File::create("output.pdf").unwrap(); | ||||
|     output.write(&pdf_data).unwrap(); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user