| use doc::ItemUtils; |
| use io::ReaderUtil; |
| |
| export WriteInstr; |
| export Writer; |
| export WriterFactory; |
| export WriterUtils; |
| export make_writer_factory; |
| export future_writer_factory; |
| export make_filename; |
| |
| enum WriteInstr { |
| Write(~str), |
| Done |
| } |
| |
| type Writer = fn~(+v: WriteInstr); |
| type WriterFactory = fn~(page: doc::Page) -> Writer; |
| |
| trait WriterUtils { |
| fn write_str(str: ~str); |
| fn write_line(str: ~str); |
| fn write_done(); |
| } |
| |
| impl Writer: WriterUtils { |
| fn write_str(str: ~str) { |
| self(Write(str)); |
| } |
| |
| fn write_line(str: ~str) { |
| self.write_str(str + ~"\n"); |
| } |
| |
| fn write_done() { |
| self(Done) |
| } |
| } |
| |
| 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 { |
| fn~(page: doc::Page) -> Writer { |
| markdown_writer(config, page) |
| } |
| } |
| |
| fn pandoc_writer_factory(config: config::Config) -> WriterFactory { |
| fn~(page: doc::Page) -> Writer { |
| pandoc_writer(config, page) |
| } |
| } |
| |
| 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(); |
| 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| { |
| use io::WriterUtil; |
| |
| debug!("pandoc cmd: %s", pandoc_cmd); |
| debug!("pandoc args: %s", str::connect(pandoc_args, ~" ")); |
| |
| let pipe_in = os::pipe(); |
| let pipe_out = os::pipe(); |
| let pipe_err = os::pipe(); |
| let pid = run::spawn_process( |
| pandoc_cmd, pandoc_args, &None, &None, |
| pipe_in.in, pipe_out.out, pipe_err.out); |
| |
| let writer = io::fd_writer(pipe_in.out, false); |
| writer.write_str(markdown); |
| |
| os::close(pipe_in.in); |
| os::close(pipe_out.out); |
| os::close(pipe_err.out); |
| os::close(pipe_in.out); |
| |
| let stdout_po = comm::Port(); |
| let stdout_ch = comm::Chan(&stdout_po); |
| do task::spawn_sched(task::SingleThreaded) { |
| comm::send(stdout_ch, readclose(pipe_out.in)); |
| } |
| let stdout = comm::recv(stdout_po); |
| |
| let stderr_po = comm::Port(); |
| let stderr_ch = comm::Chan(&stderr_po); |
| do task::spawn_sched(task::SingleThreaded) { |
| comm::send(stderr_ch, readclose(pipe_err.in)); |
| } |
| let stderr = comm::recv(stderr_po); |
| |
| let status = run::waitpid(pid); |
| debug!("pandoc result: %i", status); |
| if status != 0 { |
| error!("pandoc-out: %s", stdout); |
| error!("pandoc-err: %s", stderr); |
| fail ~"pandoc failed"; |
| } |
| } |
| } |
| |
| fn readclose(fd: libc::c_int) -> ~str { |
| // Copied from run::program_output |
| let file = os::fdopen(fd); |
| let reader = io::FILE_reader(file, false); |
| let mut buf = ~""; |
| while !reader.eof() { |
| let bytes = reader.read_bytes(4096u); |
| buf += str::from_bytes(bytes); |
| } |
| os::fclose(file); |
| return buf; |
| } |
| |
| fn generic_writer(+process: fn~(markdown: ~str)) -> Writer { |
| let ch = do task::spawn_listener |po: comm::Port<WriteInstr>| { |
| let mut markdown = ~""; |
| let mut keep_going = true; |
| while keep_going { |
| match comm::recv(po) { |
| Write(s) => markdown += s, |
| Done => keep_going = false |
| } |
| } |
| process(markdown); |
| }; |
| |
| fn~(+instr: WriteInstr) { |
| comm::send(ch, instr); |
| } |
| } |
| |
| fn make_local_filename( |
| config: config::Config, |
| page: doc::Page |
| ) -> Path { |
| let filename = make_filename(config, page); |
| config.output_dir.push_rel(&filename) |
| } |
| |
| 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) => { |
| str::connect(doc.path() + ~[doc.name()], ~"_") |
| } |
| } |
| }; |
| let ext = match config.output_format { |
| config::Markdown => ~"md", |
| config::PandocHtml => ~"html" |
| }; |
| |
| Path(filename).with_filetype(ext) |
| } |
| |
| #[test] |
| fn should_use_markdown_file_name_based_off_crate() { |
| let config = { |
| output_dir: Path("output/dir"), |
| output_format: config::Markdown, |
| output_style: config::DocPerCrate, |
| .. config::default_config(&Path("input/test.rc")) |
| }; |
| let doc = test::mk_doc(~"test", ~""); |
| let page = doc::CratePage(doc.CrateDoc()); |
| let filename = make_local_filename(config, page); |
| assert filename.to_str() == ~"output/dir/test.md"; |
| } |
| |
| #[test] |
| fn should_name_html_crate_file_name_index_html_when_doc_per_mod() { |
| let config = { |
| output_dir: Path("output/dir"), |
| output_format: config::PandocHtml, |
| output_style: config::DocPerMod, |
| .. config::default_config(&Path("input/test.rc")) |
| }; |
| let doc = test::mk_doc(~"", ~""); |
| let page = doc::CratePage(doc.CrateDoc()); |
| let filename = make_local_filename(config, page); |
| assert filename.to_str() == ~"output/dir/index.html"; |
| } |
| |
| #[test] |
| fn should_name_mod_file_names_by_path() { |
| let config = { |
| output_dir: Path("output/dir"), |
| output_format: config::PandocHtml, |
| output_style: config::DocPerMod, |
| .. config::default_config(&Path("input/test.rc")) |
| }; |
| let doc = test::mk_doc(~"", ~"mod a { mod b { } }"); |
| let modb = doc.cratemod().mods()[0].mods()[0]; |
| let page = doc::ItemPage(doc::ModTag(modb)); |
| let filename = make_local_filename(config, page); |
| assert filename == Path("output/dir/a_b.html"); |
| } |
| |
| #[cfg(test)] |
| mod test { |
| #[legacy_exports]; |
| fn mk_doc(name: ~str, source: ~str) -> doc::Doc { |
| do astsrv::from_str(source) |srv| { |
| let doc = extract::from_srv(srv, name); |
| let doc = path_pass::mk_pass().f(srv, doc); |
| doc |
| } |
| } |
| } |
| |
| fn write_file(path: &Path, s: ~str) { |
| use io::WriterUtil; |
| |
| match io::file_writer(path, ~[io::Create, io::Truncate]) { |
| result::Ok(writer) => { |
| writer.write_str(s); |
| } |
| result::Err(e) => fail e |
| } |
| } |
| |
| fn future_writer_factory( |
| ) -> (WriterFactory, comm::Port<(doc::Page, ~str)>) { |
| let markdown_po = comm::Port(); |
| let markdown_ch = comm::Chan(&markdown_po); |
| let writer_factory = fn~(page: doc::Page) -> Writer { |
| let writer_po = comm::Port(); |
| let writer_ch = comm::Chan(&writer_po); |
| do task::spawn { |
| let (writer, future) = future_writer(); |
| comm::send(writer_ch, writer); |
| let s = future::get(&future); |
| comm::send(markdown_ch, (page, s)); |
| } |
| comm::recv(writer_po) |
| }; |
| |
| (writer_factory, markdown_po) |
| } |
| |
| fn future_writer() -> (Writer, future::Future<~str>) { |
| let (chan, port) = pipes::stream(); |
| let writer = fn~(+instr: WriteInstr) { |
| chan.send(copy instr); |
| }; |
| let future = do future::from_fn { |
| let mut res = ~""; |
| loop { |
| match port.recv() { |
| Write(s) => res += s, |
| Done => break |
| } |
| } |
| res |
| }; |
| (writer, future) |
| } |