// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use config; use doc::ItemUtils; use doc; use std::comm::*; use std::comm; use std::io; use std::result; use std::run; use std::str; use std::task; use extra::future; #[deriving(Clone)] pub enum WriteInstr { Write(~str), Done } pub type Writer = ~fn(v: WriteInstr); pub type WriterFactory = ~fn(page: doc::Page) -> Writer; pub trait WriterUtils { fn put_str(&self, str: ~str); fn put_line(&self, str: ~str); fn put_done(&self); } impl WriterUtils for Writer { fn put_str(&self, str: ~str) { (*self)(Write(str)); } fn put_line(&self, str: ~str) { self.put_str(str + "\n"); } fn put_done(&self) { (*self)(Done) } } pub fn make_writer_factory(config: config::Config) -> WriterFactory { match config.output_format { config::Markdown => { markdown_writer_factory(config) } config::PandocHtml => { pandoc_writer_factory(config) } } } fn markdown_writer_factory(config: config::Config) -> WriterFactory { let result: ~fn(page: doc::Page) -> Writer = |page| { markdown_writer(&config, page) }; result } fn pandoc_writer_factory(config: config::Config) -> WriterFactory { let result: ~fn(doc::Page) -> Writer = |page| { pandoc_writer(&config, page) }; result } fn markdown_writer( config: &config::Config, page: doc::Page ) -> Writer { let filename = make_local_filename(config, page); do generic_writer |markdown| { write_file(&filename, markdown); } } fn pandoc_writer( config: &config::Config, page: doc::Page ) -> Writer { assert!(config.pandoc_cmd.is_some()); let pandoc_cmd = (*config.pandoc_cmd.get_ref()).clone(); let filename = make_local_filename(config, page); let pandoc_args = ~[ ~"--standalone", ~"--section-divs", ~"--from=markdown", ~"--to=html", ~"--css=rust.css", ~"--output=" + filename.to_str() ]; do generic_writer |markdown| { debug!("pandoc cmd: %s", pandoc_cmd); debug!("pandoc args: %s", pandoc_args.connect(" ")); let proc = run::Process::new(pandoc_cmd, pandoc_args, run::ProcessOptions::new()); let mut proc = proc.unwrap(); proc.input().write(markdown.as_bytes()); let output = proc.finish_with_output(); debug!("pandoc result: %i", output.status); if output.status != 0 { error!("pandoc-out: %s", str::from_bytes(output.output)); error!("pandoc-err: %s", str::from_bytes(output.error)); fail!("pandoc failed"); } } } fn generic_writer(process: ~fn(markdown: ~str)) -> Writer { let (po, ch) = stream::(); do task::spawn || { let mut markdown = ~""; let mut keep_going = true; while keep_going { match po.recv() { Write(s) => markdown.push_str(s), Done => keep_going = false } } process(markdown); }; let result: ~fn(instr: WriteInstr) = |instr| ch.send(instr); result } pub fn make_local_filename( config: &config::Config, page: doc::Page ) -> Path { let filename = make_filename(config, page); config.output_dir.push_rel(&filename) } pub fn make_filename( config: &config::Config, page: doc::Page ) -> Path { let filename = { match page { doc::CratePage(doc) => { if config.output_format == config::PandocHtml && config.output_style == config::DocPerMod { ~"index" } else { assert!(doc.topmod.name_() != ~""); doc.topmod.name_() } } doc::ItemPage(doc) => { (doc.path() + &[doc.name_()]).connect("_") } } }; let ext = match config.output_format { config::Markdown => ~"md", config::PandocHtml => ~"html" }; Path(filename).with_filetype(ext) } fn write_file(path: &Path, s: ~str) { use std::io::WriterUtil; match io::file_writer(path, [io::Create, io::Truncate]) { result::Ok(writer) => { writer.write_str(s); } result::Err(e) => fail!(e) } } pub fn future_writer_factory( ) -> (WriterFactory, Port<(doc::Page, ~str)>) { let (markdown_po, markdown_ch) = stream(); let markdown_ch = SharedChan::new(markdown_ch); let writer_factory: WriterFactory = |page| { let (writer_po, writer_ch) = comm::stream(); let markdown_ch = markdown_ch.clone(); do task::spawn || { let (writer, future) = future_writer(); let mut future = future; writer_ch.send(writer); let s = future.get(); markdown_ch.send((page.clone(), s)); } writer_po.recv() }; (writer_factory, markdown_po) } fn future_writer() -> (Writer, future::Future<~str>) { let (port, chan) = comm::stream(); let writer: ~fn(instr: WriteInstr) = |instr| chan.send(instr.clone()); let future = do future::from_fn || { let mut res = ~""; loop { match port.recv() { Write(s) => res.push_str(s), Done => break } } res }; (writer, future) } #[cfg(test)] mod test { use astsrv; use doc; use extract; use path_pass; use config; use super::make_local_filename; fn mk_doc(name: ~str, source: ~str) -> doc::Doc { do astsrv::from_str(source) |srv| { let doc = extract::from_srv(srv.clone(), name.clone()); let doc = (path_pass::mk_pass().f)(srv.clone(), doc); doc } } #[test] fn should_use_markdown_file_name_based_off_crate() { let config = config::Config { output_dir: Path("output/dir"), output_format: config::Markdown, output_style: config::DocPerCrate, .. config::default_config(&Path("input/test.rc")) }; let doc = mk_doc(~"test", ~""); let page = doc::CratePage(doc.CrateDoc()); let filename = make_local_filename(&config, page); assert_eq!(filename.to_str(), ~"output/dir/test.md"); } #[test] fn should_name_html_crate_file_name_index_html_when_doc_per_mod() { let config = config::Config { output_dir: Path("output/dir"), output_format: config::PandocHtml, output_style: config::DocPerMod, .. config::default_config(&Path("input/test.rc")) }; let doc = mk_doc(~"", ~""); let page = doc::CratePage(doc.CrateDoc()); let filename = make_local_filename(&config, page); assert_eq!(filename.to_str(), ~"output/dir/index.html"); } #[test] fn should_name_mod_file_names_by_path() { let config = config::Config { output_dir: Path("output/dir"), output_format: config::PandocHtml, output_style: config::DocPerMod, .. config::default_config(&Path("input/test.rc")) }; let doc = mk_doc(~"", ~"mod a { mod b { } }"); // hidden __std_macros module at the start. let modb = doc.cratemod().mods()[1].mods()[0].clone(); let page = doc::ItemPage(doc::ModTag(modb)); let filename = make_local_filename(&config, page); assert_eq!(filename, Path("output/dir/a_b.html")); } }