// Copyright 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. /*! This modules provides bindings to the local event loop's TTY interface, using it to have synchronous, but non-blocking versions of stdio. These handles can be inspected for information about terminal dimensions or related information about the stream or terminal that it is attached to. # Example ```rust use std::rt::io; let mut out = io::stdout(); out.write(bytes!("Hello, world!")); ``` */ use fmt; use libc; use option::{Option, Some, None}; use result::{Ok, Err}; use rt::rtio::{IoFactory, RtioTTY, RtioFileStream, with_local_io, CloseAsynchronously}; use super::{Reader, Writer, io_error, IoError, OtherIoError}; // And so begins the tale of acquiring a uv handle to a stdio stream on all // platforms in all situations. Our story begins by splitting the world into two // categories, windows and unix. Then one day the creators of unix said let // there be redirection! And henceforth there was redirection away from the // console for standard I/O streams. // // After this day, the world split into four factions: // // 1. Unix with stdout on a terminal. // 2. Unix with stdout redirected. // 3. Windows with stdout on a terminal. // 4. Windows with stdout redirected. // // Many years passed, and then one day the nation of libuv decided to unify this // world. After months of toiling, uv created three ideas: TTY, Pipe, File. // These three ideas propagated throughout the lands and the four great factions // decided to settle among them. // // The groups of 1, 2, and 3 all worked very hard towards the idea of TTY. Upon // doing so, they even enhanced themselves further then their Pipe/File // brethren, becoming the dominant powers. // // The group of 4, however, decided to work independently. They abandoned the // common TTY belief throughout, and even abandoned the fledgling Pipe belief. // The members of the 4th faction decided to only align themselves with File. // // tl;dr; TTY works on everything but when windows stdout is redirected, in that // case pipe also doesn't work, but magically file does! enum StdSource { TTY(~RtioTTY), File(~RtioFileStream), } #[fixed_stack_segment] #[inline(never)] fn src(fd: libc::c_int, readable: bool, f: &fn(StdSource) -> T) -> T { do with_local_io |io| { let fd = unsafe { libc::dup(fd) }; match io.tty_open(fd, readable) { Ok(tty) => Some(f(TTY(tty))), Err(_) => { // It's not really that desirable if these handles are closed // synchronously, and because they're squirreled away in a task // structure the destructors will be run when the task is // attempted to get destroyed. This means that if we run a // synchronous destructor we'll attempt to do some scheduling // operations which will just result in sadness. Some(f(File(io.fs_from_raw_fd(fd, CloseAsynchronously)))) } } }.unwrap() } /// Creates a new non-blocking handle to the stdin of the current process. /// /// See `stdout()` for notes about this function. #[fixed_stack_segment] #[inline(never)] pub fn stdin() -> StdReader { do src(libc::STDIN_FILENO, true) |src| { StdReader { inner: src } } } /// Creates a new non-blocking handle to the stdout of the current process. /// /// Note that this is a fairly expensive operation in that at least one memory /// allocation is performed. Additionally, this must be called from a runtime /// task context because the stream returned will be a non-blocking object using /// the local scheduler to perform the I/O. pub fn stdout() -> StdWriter { do src(libc::STDOUT_FILENO, false) |src| { StdWriter { inner: src } } } /// Creates a new non-blocking handle to the stderr of the current process. /// /// See `stdout()` for notes about this function. pub fn stderr() -> StdWriter { do src(libc::STDERR_FILENO, false) |src| { StdWriter { inner: src } } } /// Prints a string to the stdout of the current process. No newline is emitted /// after the string is printed. pub fn print(s: &str) { // XXX: need to see if not caching stdin() is the cause of performance // issues, it should be possible to cache a stdout handle in each Task // and then re-use that across calls to print/println. Note that the // resolution of this comment will affect all of the prints below as // well. stdout().write(s.as_bytes()); } /// Prints a string as a line. to the stdout of the current process. A literal /// `\n` character is printed to the console after the string. pub fn println(s: &str) { let mut out = stdout(); out.write(s.as_bytes()); out.write(['\n' as u8]); } /// Similar to `print`, but takes a `fmt::Arguments` structure to be compatible /// with the `format_args!` macro. pub fn print_args(fmt: &fmt::Arguments) { let mut out = stdout(); fmt::write(&mut out as &mut Writer, fmt); } /// Similar to `println`, but takes a `fmt::Arguments` structure to be /// compatible with the `format_args!` macro. pub fn println_args(fmt: &fmt::Arguments) { let mut out = stdout(); fmt::writeln(&mut out as &mut Writer, fmt); } /// Representation of a reader of a standard input stream pub struct StdReader { priv inner: StdSource } impl Reader for StdReader { fn read(&mut self, buf: &mut [u8]) -> Option { let ret = match self.inner { TTY(ref mut tty) => tty.read(buf), File(ref mut file) => file.read(buf).map_move(|i| i as uint), }; match ret { Ok(amt) => Some(amt as uint), Err(e) => { io_error::cond.raise(e); None } } } fn eof(&mut self) -> bool { false } } /// Representation of a writer to a standard output stream pub struct StdWriter { priv inner: StdSource } impl StdWriter { /// Gets the size of this output window, if possible. This is typically used /// when the writer is attached to something like a terminal, this is used /// to fetch the dimensions of the terminal. /// /// If successful, returns Some((width, height)). /// /// # Failure /// /// This function will raise on the `io_error` condition if an error /// happens. pub fn winsize(&mut self) -> Option<(int, int)> { match self.inner { TTY(ref mut tty) => { match tty.get_winsize() { Ok(p) => Some(p), Err(e) => { io_error::cond.raise(e); None } } } File(*) => { io_error::cond.raise(IoError { kind: OtherIoError, desc: "stream is not a tty", detail: None, }); None } } } /// Controls whether this output stream is a "raw stream" or simply a normal /// stream. /// /// # Failure /// /// This function will raise on the `io_error` condition if an error /// happens. pub fn set_raw(&mut self, raw: bool) { match self.inner { TTY(ref mut tty) => { match tty.set_raw(raw) { Ok(()) => {}, Err(e) => io_error::cond.raise(e), } } File(*) => { io_error::cond.raise(IoError { kind: OtherIoError, desc: "stream is not a tty", detail: None, }); } } } /// Returns whether this tream is attached to a TTY instance or not. /// /// This is similar to libc's isatty() function pub fn isatty(&self) -> bool { match self.inner { TTY(ref tty) => tty.isatty(), File(*) => false, } } } impl Writer for StdWriter { fn write(&mut self, buf: &[u8]) { let ret = match self.inner { TTY(ref mut tty) => tty.write(buf), File(ref mut file) => file.write(buf), }; match ret { Ok(()) => {} Err(e) => io_error::cond.raise(e) } } fn flush(&mut self) { /* nothing to do */ } } #[cfg(test)] mod tests { use super::*; #[test] fn smoke() { // Just make sure we can acquire handles stdin(); stdout(); stderr(); } }