| /*! |
| |
| Basic input/output |
| |
| */ |
| |
| #[forbid(deprecated_mode)]; |
| #[forbid(deprecated_pattern)]; |
| |
| use result::Result; |
| |
| use cmp::Eq; |
| use dvec::DVec; |
| use libc::{c_int, c_long, c_uint, c_void, size_t, ssize_t}; |
| use libc::consts::os::posix88::*; |
| use libc::consts::os::extra::*; |
| |
| #[allow(non_camel_case_types)] // not sure what to do about this |
| type fd_t = c_int; |
| |
| #[abi = "cdecl"] |
| extern mod rustrt { |
| fn rust_get_stdin() -> *libc::FILE; |
| fn rust_get_stdout() -> *libc::FILE; |
| fn rust_get_stderr() -> *libc::FILE; |
| } |
| |
| // Reading |
| |
| // FIXME (#2004): This is all buffered. We might need an unbuffered variant |
| // as well |
| pub enum SeekStyle { SeekSet, SeekEnd, SeekCur, } |
| |
| |
| // The raw underlying reader trait. All readers must implement this. |
| pub trait Reader { |
| // FIXME (#2004): Seekable really should be orthogonal. |
| |
| // FIXME (#2982): This should probably return an error. |
| fn read(buf: &[mut u8], len: uint) -> uint; |
| fn read_byte() -> int; |
| fn unread_byte(int); |
| fn eof() -> bool; |
| fn seek(int, SeekStyle); |
| fn tell() -> uint; |
| } |
| |
| // Generic utility functions defined on readers |
| |
| pub trait ReaderUtil { |
| fn read_bytes(len: uint) -> ~[u8]; |
| fn read_line() -> ~str; |
| |
| fn read_chars(n: uint) -> ~[char]; |
| fn read_char() -> char; |
| fn read_c_str() -> ~str; |
| fn read_le_uint(size: uint) -> uint; |
| fn read_le_int(size: uint) -> int; |
| fn read_be_uint(size: uint) -> uint; |
| fn read_whole_stream() -> ~[u8]; |
| fn each_byte(it: fn(int) -> bool); |
| fn each_char(it: fn(char) -> bool); |
| fn each_line(it: fn((&str)) -> bool); |
| } |
| |
| impl<T: Reader> T : ReaderUtil { |
| fn read_bytes(len: uint) -> ~[u8] { |
| let mut buf = vec::with_capacity(len); |
| unsafe { vec::raw::set_len(&mut buf, len); } |
| |
| let count = self.read(buf, len); |
| |
| unsafe { vec::raw::set_len(&mut buf, count); } |
| move buf |
| } |
| fn read_line() -> ~str { |
| let mut buf = ~[]; |
| loop { |
| let ch = self.read_byte(); |
| if ch == -1 || ch == 10 { break; } |
| buf.push(ch as u8); |
| } |
| str::from_bytes(buf) |
| } |
| |
| fn read_chars(n: uint) -> ~[char] { |
| // returns the (consumed offset, n_req), appends characters to &chars |
| fn chars_from_bytes<T: Reader>(buf: &~[u8], chars: &mut ~[char]) |
| -> (uint, uint) { |
| let mut i = 0; |
| let buf_len = buf.len(); |
| while i < buf_len { |
| let b0 = buf[i]; |
| let w = str::utf8_char_width(b0); |
| let end = i + w; |
| i += 1; |
| assert (w > 0); |
| if w == 1 { |
| chars.push(b0 as char); |
| loop; |
| } |
| // can't satisfy this char with the existing data |
| if end > buf_len { |
| return (i - 1, end - buf_len); |
| } |
| let mut val = 0; |
| while i < end { |
| let next = buf[i] as int; |
| i += 1; |
| assert (next > -1); |
| assert (next & 192 == 128); |
| val <<= 6; |
| val += (next & 63) as uint; |
| } |
| // See str::char_at |
| val += ((b0 << ((w + 1) as u8)) as uint) |
| << (w - 1) * 6 - w - 1u; |
| chars.push(val as char); |
| } |
| return (i, 0); |
| } |
| let mut buf: ~[u8] = ~[]; |
| let mut chars: ~[char] = ~[]; |
| // might need more bytes, but reading n will never over-read |
| let mut nbread = n; |
| while nbread > 0 { |
| let data = self.read_bytes(nbread); |
| if data.is_empty() { |
| // eof - FIXME (#2004): should we do something if |
| // we're split in a unicode char? |
| break; |
| } |
| buf.push_all(data); |
| let (offset, nbreq) = chars_from_bytes::<T>(&buf, &mut chars); |
| let ncreq = n - chars.len(); |
| // again we either know we need a certain number of bytes |
| // to complete a character, or we make sure we don't |
| // over-read by reading 1-byte per char needed |
| nbread = if ncreq > nbreq { ncreq } else { nbreq }; |
| if nbread > 0 { |
| buf = vec::slice(buf, offset, buf.len()); |
| } |
| } |
| move chars |
| } |
| |
| fn read_char() -> char { |
| let c = self.read_chars(1); |
| if vec::len(c) == 0 { |
| return -1 as char; // FIXME will this stay valid? // #2004 |
| } |
| assert(vec::len(c) == 1); |
| return c[0]; |
| } |
| |
| fn read_c_str() -> ~str { |
| let mut buf: ~[u8] = ~[]; |
| loop { |
| let ch = self.read_byte(); |
| if ch < 1 { break; } else { buf.push(ch as u8); } |
| } |
| str::from_bytes(buf) |
| } |
| |
| // FIXME deal with eof? // #2004 |
| fn read_le_uint(size: uint) -> uint { |
| let mut val = 0u, pos = 0u, i = size; |
| while i > 0u { |
| val += (self.read_byte() as uint) << pos; |
| pos += 8u; |
| i -= 1u; |
| } |
| val |
| } |
| fn read_le_int(size: uint) -> int { |
| let mut val = 0u, pos = 0u, i = size; |
| while i > 0u { |
| val += (self.read_byte() as uint) << pos; |
| pos += 8u; |
| i -= 1u; |
| } |
| val as int |
| } |
| fn read_be_uint(size: uint) -> uint { |
| let mut val = 0u, i = size; |
| while i > 0u { |
| i -= 1u; |
| val += (self.read_byte() as uint) << i * 8u; |
| } |
| val |
| } |
| |
| fn read_whole_stream() -> ~[u8] { |
| let mut buf: ~[u8] = ~[]; |
| while !self.eof() { buf.push_all(self.read_bytes(2048u)); } |
| move buf |
| } |
| |
| fn each_byte(it: fn(int) -> bool) { |
| while !self.eof() { |
| if !it(self.read_byte()) { break; } |
| } |
| } |
| |
| fn each_char(it: fn(char) -> bool) { |
| while !self.eof() { |
| if !it(self.read_char()) { break; } |
| } |
| } |
| |
| fn each_line(it: fn(s: &str) -> bool) { |
| while !self.eof() { |
| if !it(self.read_line()) { break; } |
| } |
| } |
| } |
| |
| // Reader implementations |
| |
| fn convert_whence(whence: SeekStyle) -> i32 { |
| return match whence { |
| SeekSet => 0i32, |
| SeekCur => 1i32, |
| SeekEnd => 2i32 |
| }; |
| } |
| |
| impl *libc::FILE: Reader { |
| fn read(buf: &[mut u8], len: uint) -> uint { |
| do vec::as_mut_buf(buf) |buf_p, buf_len| { |
| assert buf_len <= len; |
| |
| let count = libc::fread(buf_p as *mut c_void, 1u as size_t, |
| len as size_t, self); |
| |
| count as uint |
| } |
| } |
| fn read_byte() -> int { return libc::fgetc(self) as int; } |
| fn unread_byte(byte: int) { libc::ungetc(byte as c_int, self); } |
| fn eof() -> bool { return libc::feof(self) != 0 as c_int; } |
| fn seek(offset: int, whence: SeekStyle) { |
| assert libc::fseek(self, offset as c_long, convert_whence(whence)) |
| == 0 as c_int; |
| } |
| fn tell() -> uint { return libc::ftell(self) as uint; } |
| } |
| |
| // A forwarding impl of reader that also holds on to a resource for the |
| // duration of its lifetime. |
| // FIXME there really should be a better way to do this // #2004 |
| impl<T: Reader, C> {base: T, cleanup: C}: Reader { |
| fn read(buf: &[mut u8], len: uint) -> uint { self.base.read(buf, len) } |
| fn read_byte() -> int { self.base.read_byte() } |
| fn unread_byte(byte: int) { self.base.unread_byte(byte); } |
| fn eof() -> bool { self.base.eof() } |
| fn seek(off: int, whence: SeekStyle) { self.base.seek(off, whence) } |
| fn tell() -> uint { self.base.tell() } |
| } |
| |
| struct FILERes { |
| f: *libc::FILE, |
| drop { libc::fclose(self.f); } |
| } |
| |
| fn FILERes(f: *libc::FILE) -> FILERes { |
| FILERes { |
| f: f |
| } |
| } |
| |
| pub fn FILE_reader(f: *libc::FILE, cleanup: bool) -> Reader { |
| if cleanup { |
| {base: f, cleanup: FILERes(f)} as Reader |
| } else { |
| f as Reader |
| } |
| } |
| |
| // FIXME (#2004): this should either be an trait-less impl, a set of |
| // top-level functions that take a reader, or a set of default methods on |
| // reader (which can then be called reader) |
| |
| pub fn stdin() -> Reader { rustrt::rust_get_stdin() as Reader } |
| |
| pub fn file_reader(path: &Path) -> Result<Reader, ~str> { |
| let f = os::as_c_charp(path.to_str(), |pathbuf| { |
| os::as_c_charp("r", |modebuf| |
| libc::fopen(pathbuf, modebuf) |
| ) |
| }); |
| return if f as uint == 0u { result::Err(~"error opening " |
| + path.to_str()) } |
| else { |
| result::Ok(FILE_reader(f, true)) |
| } |
| } |
| |
| |
| // Byte buffer readers |
| |
| pub type ByteBuf = {buf: &[const u8], mut pos: uint}; |
| |
| impl ByteBuf: Reader { |
| fn read(buf: &[mut u8], len: uint) -> uint { |
| let count = uint::min(len, self.buf.len() - self.pos); |
| |
| let view = vec::const_view(self.buf, self.pos, self.buf.len()); |
| vec::bytes::memcpy(buf, view, count); |
| |
| self.pos += count; |
| |
| count |
| } |
| fn read_byte() -> int { |
| if self.pos == self.buf.len() { return -1; } |
| let b = self.buf[self.pos]; |
| self.pos += 1u; |
| return b as int; |
| } |
| // FIXME (#2738): implement this |
| fn unread_byte(_byte: int) { error!("Unimplemented: unread_byte"); fail; } |
| fn eof() -> bool { self.pos == self.buf.len() } |
| fn seek(offset: int, whence: SeekStyle) { |
| let pos = self.pos; |
| self.pos = seek_in_buf(offset, pos, self.buf.len(), whence); |
| } |
| fn tell() -> uint { self.pos } |
| } |
| |
| pub fn with_bytes_reader<t>(bytes: &[u8], f: fn(Reader) -> t) -> t { |
| f({buf: bytes, mut pos: 0u} as Reader) |
| } |
| |
| pub fn with_str_reader<T>(s: &str, f: fn(Reader) -> T) -> T { |
| str::byte_slice(s, |bytes| with_bytes_reader(bytes, f)) |
| } |
| |
| // Writing |
| pub enum FileFlag { Append, Create, Truncate, NoFlag, } |
| |
| // What type of writer are we? |
| pub enum WriterType { Screen, File } |
| |
| pub impl WriterType : Eq { |
| pure fn eq(other: &WriterType) -> bool { |
| match (self, (*other)) { |
| (Screen, Screen) | (File, File) => true, |
| (Screen, _) | (File, _) => false |
| } |
| } |
| pure fn ne(other: &WriterType) -> bool { !self.eq(other) } |
| } |
| |
| // FIXME (#2004): Seekable really should be orthogonal. |
| // FIXME (#2004): eventually u64 |
| pub trait Writer { |
| fn write(v: &[const u8]); |
| fn seek(int, SeekStyle); |
| fn tell() -> uint; |
| fn flush() -> int; |
| fn get_type() -> WriterType; |
| } |
| |
| impl<T: Writer, C> {base: T, cleanup: C}: Writer { |
| fn write(bs: &[const u8]) { self.base.write(bs); } |
| fn seek(off: int, style: SeekStyle) { self.base.seek(off, style); } |
| fn tell() -> uint { self.base.tell() } |
| fn flush() -> int { self.base.flush() } |
| fn get_type() -> WriterType { File } |
| } |
| |
| impl *libc::FILE: Writer { |
| fn write(v: &[const u8]) { |
| do vec::as_const_buf(v) |vbuf, len| { |
| let nout = libc::fwrite(vbuf as *c_void, len as size_t, |
| 1u as size_t, self); |
| if nout < 1 as size_t { |
| error!("error writing buffer"); |
| log(error, os::last_os_error()); |
| fail; |
| } |
| } |
| } |
| fn seek(offset: int, whence: SeekStyle) { |
| assert libc::fseek(self, offset as c_long, convert_whence(whence)) |
| == 0 as c_int; |
| } |
| fn tell() -> uint { libc::ftell(self) as uint } |
| fn flush() -> int { libc::fflush(self) as int } |
| fn get_type() -> WriterType { |
| let fd = libc::fileno(self); |
| if libc::isatty(fd) == 0 { File } |
| else { Screen } |
| } |
| } |
| |
| pub fn FILE_writer(f: *libc::FILE, cleanup: bool) -> Writer { |
| if cleanup { |
| {base: f, cleanup: FILERes(f)} as Writer |
| } else { |
| f as Writer |
| } |
| } |
| |
| impl fd_t: Writer { |
| fn write(v: &[const u8]) { |
| let mut count = 0u; |
| do vec::as_const_buf(v) |vbuf, len| { |
| while count < len { |
| let vb = ptr::const_offset(vbuf, count) as *c_void; |
| let nout = libc::write(self, vb, len as size_t); |
| if nout < 0 as ssize_t { |
| error!("error writing buffer"); |
| log(error, os::last_os_error()); |
| fail; |
| } |
| count += nout as uint; |
| } |
| } |
| } |
| fn seek(_offset: int, _whence: SeekStyle) { |
| error!("need 64-bit foreign calls for seek, sorry"); |
| fail; |
| } |
| fn tell() -> uint { |
| error!("need 64-bit foreign calls for tell, sorry"); |
| fail; |
| } |
| fn flush() -> int { 0 } |
| fn get_type() -> WriterType { |
| if libc::isatty(self) == 0 { File } else { Screen } |
| } |
| } |
| |
| struct FdRes { |
| fd: fd_t, |
| drop { libc::close(self.fd); } |
| } |
| |
| fn FdRes(fd: fd_t) -> FdRes { |
| FdRes { |
| fd: fd |
| } |
| } |
| |
| pub fn fd_writer(fd: fd_t, cleanup: bool) -> Writer { |
| if cleanup { |
| {base: fd, cleanup: FdRes(fd)} as Writer |
| } else { |
| fd as Writer |
| } |
| } |
| |
| |
| pub fn mk_file_writer(path: &Path, flags: &[FileFlag]) |
| -> Result<Writer, ~str> { |
| |
| #[cfg(windows)] |
| fn wb() -> c_int { (O_WRONLY | O_BINARY) as c_int } |
| |
| #[cfg(unix)] |
| fn wb() -> c_int { O_WRONLY as c_int } |
| |
| let mut fflags: c_int = wb(); |
| for vec::each(flags) |f| { |
| match *f { |
| Append => fflags |= O_APPEND as c_int, |
| Create => fflags |= O_CREAT as c_int, |
| Truncate => fflags |= O_TRUNC as c_int, |
| NoFlag => () |
| } |
| } |
| let fd = do os::as_c_charp(path.to_str()) |pathbuf| { |
| libc::open(pathbuf, fflags, |
| (S_IRUSR | S_IWUSR) as c_int) |
| }; |
| if fd < (0 as c_int) { |
| result::Err(fmt!("error opening %s: %s", path.to_str(), |
| os::last_os_error())) |
| } else { |
| result::Ok(fd_writer(fd, true)) |
| } |
| } |
| |
| pub fn u64_to_le_bytes<T>(n: u64, size: uint, |
| f: fn(v: &[u8]) -> T) -> T { |
| assert size <= 8u; |
| match size { |
| 1u => f(&[n as u8]), |
| 2u => f(&[n as u8, |
| (n >> 8) as u8]), |
| 4u => f(&[n as u8, |
| (n >> 8) as u8, |
| (n >> 16) as u8, |
| (n >> 24) as u8]), |
| 8u => f(&[n as u8, |
| (n >> 8) as u8, |
| (n >> 16) as u8, |
| (n >> 24) as u8, |
| (n >> 32) as u8, |
| (n >> 40) as u8, |
| (n >> 48) as u8, |
| (n >> 56) as u8]), |
| _ => { |
| |
| let mut bytes: ~[u8] = ~[], i = size, n = n; |
| while i > 0u { |
| bytes.push((n & 255_u64) as u8); |
| n >>= 8_u64; |
| i -= 1u; |
| } |
| f(bytes) |
| } |
| } |
| } |
| |
| pub fn u64_to_be_bytes<T>(n: u64, size: uint, |
| f: fn(v: &[u8]) -> T) -> T { |
| assert size <= 8u; |
| match size { |
| 1u => f(&[n as u8]), |
| 2u => f(&[(n >> 8) as u8, |
| n as u8]), |
| 4u => f(&[(n >> 24) as u8, |
| (n >> 16) as u8, |
| (n >> 8) as u8, |
| n as u8]), |
| 8u => f(&[(n >> 56) as u8, |
| (n >> 48) as u8, |
| (n >> 40) as u8, |
| (n >> 32) as u8, |
| (n >> 24) as u8, |
| (n >> 16) as u8, |
| (n >> 8) as u8, |
| n as u8]), |
| _ => { |
| let mut bytes: ~[u8] = ~[]; |
| let mut i = size; |
| while i > 0u { |
| let shift = ((i - 1u) * 8u) as u64; |
| bytes.push((n >> shift) as u8); |
| i -= 1u; |
| } |
| f(bytes) |
| } |
| } |
| } |
| |
| pub fn u64_from_be_bytes(data: &[const u8], |
| start: uint, size: uint) -> u64 { |
| let mut sz = size; |
| assert (sz <= 8u); |
| let mut val = 0_u64; |
| let mut pos = start; |
| while sz > 0u { |
| sz -= 1u; |
| val += (data[pos] as u64) << ((sz * 8u) as u64); |
| pos += 1u; |
| } |
| return val; |
| } |
| |
| // FIXME: #3048 combine trait+impl (or just move these to |
| // default methods on writer) |
| pub trait WriterUtil { |
| fn write_char(ch: char); |
| fn write_str(s: &str); |
| fn write_line(s: &str); |
| fn write_int(n: int); |
| fn write_uint(n: uint); |
| fn write_le_uint(n: uint); |
| fn write_le_int(n: int); |
| fn write_be_uint(n: uint); |
| fn write_be_int(n: int); |
| fn write_be_u64(n: u64); |
| fn write_be_u32(n: u32); |
| fn write_be_u16(n: u16); |
| fn write_be_i64(n: i64); |
| fn write_be_i32(n: i32); |
| fn write_be_i16(n: i16); |
| fn write_le_u64(n: u64); |
| fn write_le_u32(n: u32); |
| fn write_le_u16(n: u16); |
| fn write_le_i64(n: i64); |
| fn write_le_i32(n: i32); |
| fn write_le_i16(n: i16); |
| fn write_u8(n: u8); |
| } |
| |
| impl<T: Writer> T : WriterUtil { |
| fn write_char(ch: char) { |
| if ch as uint < 128u { |
| self.write(&[ch as u8]); |
| } else { |
| self.write_str(str::from_char(ch)); |
| } |
| } |
| fn write_str(s: &str) { str::byte_slice(s, |v| self.write(v)) } |
| fn write_line(s: &str) { |
| self.write_str(s); |
| self.write_str(&"\n"); |
| } |
| fn write_int(n: int) { |
| int::to_str_bytes(n, 10u, |buf| self.write(buf)) |
| } |
| fn write_uint(n: uint) { |
| uint::to_str_bytes(false, n, 10u, |buf| self.write(buf)) |
| } |
| fn write_le_uint(n: uint) { |
| u64_to_le_bytes(n as u64, uint::bytes, |v| self.write(v)) |
| } |
| fn write_le_int(n: int) { |
| u64_to_le_bytes(n as u64, int::bytes, |v| self.write(v)) |
| } |
| fn write_be_uint(n: uint) { |
| u64_to_be_bytes(n as u64, uint::bytes, |v| self.write(v)) |
| } |
| fn write_be_int(n: int) { |
| u64_to_be_bytes(n as u64, int::bytes, |v| self.write(v)) |
| } |
| fn write_be_u64(n: u64) { |
| u64_to_be_bytes(n, 8u, |v| self.write(v)) |
| } |
| fn write_be_u32(n: u32) { |
| u64_to_be_bytes(n as u64, 4u, |v| self.write(v)) |
| } |
| fn write_be_u16(n: u16) { |
| u64_to_be_bytes(n as u64, 2u, |v| self.write(v)) |
| } |
| fn write_be_i64(n: i64) { |
| u64_to_be_bytes(n as u64, 8u, |v| self.write(v)) |
| } |
| fn write_be_i32(n: i32) { |
| u64_to_be_bytes(n as u64, 4u, |v| self.write(v)) |
| } |
| fn write_be_i16(n: i16) { |
| u64_to_be_bytes(n as u64, 2u, |v| self.write(v)) |
| } |
| fn write_le_u64(n: u64) { |
| u64_to_le_bytes(n, 8u, |v| self.write(v)) |
| } |
| fn write_le_u32(n: u32) { |
| u64_to_le_bytes(n as u64, 4u, |v| self.write(v)) |
| } |
| fn write_le_u16(n: u16) { |
| u64_to_le_bytes(n as u64, 2u, |v| self.write(v)) |
| } |
| fn write_le_i64(n: i64) { |
| u64_to_le_bytes(n as u64, 8u, |v| self.write(v)) |
| } |
| fn write_le_i32(n: i32) { |
| u64_to_le_bytes(n as u64, 4u, |v| self.write(v)) |
| } |
| fn write_le_i16(n: i16) { |
| u64_to_le_bytes(n as u64, 2u, |v| self.write(v)) |
| } |
| |
| fn write_u8(n: u8) { self.write(&[n]) } |
| } |
| |
| #[allow(non_implicitly_copyable_typarams)] |
| pub fn file_writer(path: &Path, flags: &[FileFlag]) -> Result<Writer, ~str> { |
| mk_file_writer(path, flags).chain(|w| result::Ok(w)) |
| } |
| |
| |
| // FIXME: fileflags // #2004 |
| pub fn buffered_file_writer(path: &Path) -> Result<Writer, ~str> { |
| let f = do os::as_c_charp(path.to_str()) |pathbuf| { |
| do os::as_c_charp("w") |modebuf| { |
| libc::fopen(pathbuf, modebuf) |
| } |
| }; |
| return if f as uint == 0u { result::Err(~"error opening " |
| + path.to_str()) } |
| else { result::Ok(FILE_writer(f, true)) } |
| } |
| |
| // FIXME (#2004) it would be great if this could be a const |
| // FIXME (#2004) why are these different from the way stdin() is |
| // implemented? |
| pub fn stdout() -> Writer { fd_writer(libc::STDOUT_FILENO as c_int, false) } |
| pub fn stderr() -> Writer { fd_writer(libc::STDERR_FILENO as c_int, false) } |
| |
| pub fn print(s: &str) { stdout().write_str(s); } |
| pub fn println(s: &str) { stdout().write_line(s); } |
| |
| pub struct BytesWriter { |
| buf: DVec<u8>, |
| mut pos: uint, |
| } |
| |
| impl BytesWriter: Writer { |
| fn write(v: &[const u8]) { |
| do self.buf.swap |buf| { |
| let mut buf <- buf; |
| let v_len = v.len(); |
| let buf_len = buf.len(); |
| |
| let count = uint::max(buf_len, self.pos + v_len); |
| vec::reserve(&mut buf, count); |
| unsafe { vec::raw::set_len(&mut buf, count); } |
| |
| { |
| let view = vec::mut_view(buf, self.pos, count); |
| vec::bytes::memcpy(view, v, v_len); |
| } |
| |
| self.pos += v_len; |
| |
| move buf |
| } |
| } |
| fn seek(offset: int, whence: SeekStyle) { |
| let pos = self.pos; |
| let len = self.buf.len(); |
| self.pos = seek_in_buf(offset, pos, len, whence); |
| } |
| fn tell() -> uint { self.pos } |
| fn flush() -> int { 0 } |
| fn get_type() -> WriterType { File } |
| } |
| |
| impl @BytesWriter : Writer { |
| fn write(v: &[const u8]) { (*self).write(v) } |
| fn seek(offset: int, whence: SeekStyle) { (*self).seek(offset, whence) } |
| fn tell() -> uint { (*self).tell() } |
| fn flush() -> int { (*self).flush() } |
| fn get_type() -> WriterType { (*self).get_type() } |
| } |
| |
| pub fn BytesWriter() -> BytesWriter { |
| BytesWriter { buf: DVec(), mut pos: 0u } |
| } |
| |
| pub fn with_bytes_writer(f: fn(Writer)) -> ~[u8] { |
| let wr = @BytesWriter(); |
| f(wr as Writer); |
| wr.buf.check_out(|buf| buf) |
| } |
| |
| pub fn with_str_writer(f: fn(Writer)) -> ~str { |
| let mut v = with_bytes_writer(f); |
| |
| // Make sure the vector has a trailing null and is proper utf8. |
| v.push(0); |
| assert str::is_utf8(v); |
| |
| unsafe { move ::cast::transmute(v) } |
| } |
| |
| // Utility functions |
| pub fn seek_in_buf(offset: int, pos: uint, len: uint, whence: SeekStyle) -> |
| uint { |
| let mut bpos = pos as int; |
| let blen = len as int; |
| match whence { |
| SeekSet => bpos = offset, |
| SeekCur => bpos += offset, |
| SeekEnd => bpos = blen + offset |
| } |
| if bpos < 0 { bpos = 0; } else if bpos > blen { bpos = blen; } |
| return bpos as uint; |
| } |
| |
| #[allow(non_implicitly_copyable_typarams)] |
| pub fn read_whole_file_str(file: &Path) -> Result<~str, ~str> { |
| result::chain(read_whole_file(file), |bytes| { |
| if str::is_utf8(bytes) { |
| result::Ok(str::from_bytes(bytes)) |
| } else { |
| result::Err(file.to_str() + ~" is not UTF-8") |
| } |
| }) |
| } |
| |
| // FIXME (#2004): implement this in a low-level way. Going through the |
| // abstractions is pointless. |
| #[allow(non_implicitly_copyable_typarams)] |
| pub fn read_whole_file(file: &Path) -> Result<~[u8], ~str> { |
| result::chain(file_reader(file), |rdr| { |
| result::Ok(rdr.read_whole_stream()) |
| }) |
| } |
| |
| // fsync related |
| |
| pub mod fsync { |
| |
| pub enum Level { |
| // whatever fsync does on that platform |
| FSync, |
| |
| // fdatasync on linux, similiar or more on other platforms |
| FDataSync, |
| |
| // full fsync |
| // |
| // You must additionally sync the parent directory as well! |
| FullFSync, |
| } |
| |
| |
| // Artifacts that need to fsync on destruction |
| pub struct Res<t: Copy> { |
| arg: Arg<t>, |
| drop { |
| match self.arg.opt_level { |
| option::None => (), |
| option::Some(level) => { |
| // fail hard if not succesful |
| assert(self.arg.fsync_fn(self.arg.val, level) != -1); |
| } |
| } |
| } |
| } |
| |
| pub fn Res<t: Copy>(arg: Arg<t>) -> Res<t>{ |
| Res { |
| arg: move arg |
| } |
| } |
| |
| pub type Arg<t> = { |
| val: t, |
| opt_level: Option<Level>, |
| fsync_fn: fn@(f: t, Level) -> int |
| }; |
| |
| // fsync file after executing blk |
| // FIXME (#2004) find better way to create resources within lifetime of |
| // outer res |
| pub fn FILE_res_sync(file: &FILERes, opt_level: Option<Level>, |
| blk: fn(v: Res<*libc::FILE>)) { |
| blk(move Res({ |
| val: file.f, opt_level: opt_level, |
| fsync_fn: fn@(file: *libc::FILE, l: Level) -> int { |
| return os::fsync_fd(libc::fileno(file), l) as int; |
| } |
| })); |
| } |
| |
| // fsync fd after executing blk |
| pub fn fd_res_sync(fd: &FdRes, opt_level: Option<Level>, |
| blk: fn(v: Res<fd_t>)) { |
| blk(move Res({ |
| val: fd.fd, opt_level: opt_level, |
| fsync_fn: fn@(fd: fd_t, l: Level) -> int { |
| return os::fsync_fd(fd, l) as int; |
| } |
| })); |
| } |
| |
| // Type of objects that may want to fsync |
| pub trait FSyncable { fn fsync(l: Level) -> int; } |
| |
| // Call o.fsync after executing blk |
| pub fn obj_sync(o: FSyncable, opt_level: Option<Level>, |
| blk: fn(v: Res<FSyncable>)) { |
| blk(Res({ |
| val: o, opt_level: opt_level, |
| fsync_fn: fn@(o: FSyncable, l: Level) -> int { |
| return o.fsync(l); |
| } |
| })); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| |
| #[test] |
| fn test_simple() { |
| let tmpfile = &Path("tmp/lib-io-test-simple.tmp"); |
| log(debug, tmpfile); |
| let frood: ~str = |
| ~"A hoopy frood who really knows where his towel is."; |
| log(debug, frood); |
| { |
| let out: io::Writer = |
| result::get( |
| &io::file_writer(tmpfile, ~[io::Create, io::Truncate])); |
| out.write_str(frood); |
| } |
| let inp: io::Reader = result::get(&io::file_reader(tmpfile)); |
| let frood2: ~str = inp.read_c_str(); |
| log(debug, frood2); |
| assert frood == frood2; |
| } |
| |
| #[test] |
| fn test_readchars_empty() { |
| do io::with_str_reader(~"") |inp| { |
| let res : ~[char] = inp.read_chars(128); |
| assert(vec::len(res) == 0); |
| } |
| } |
| |
| #[test] |
| fn test_readchars_wide() { |
| let wide_test = ~"生锈的汤匙切肉汤hello生锈的汤匙切肉汤"; |
| let ivals : ~[int] = ~[ |
| 29983, 38152, 30340, 27748, |
| 21273, 20999, 32905, 27748, |
| 104, 101, 108, 108, 111, |
| 29983, 38152, 30340, 27748, |
| 21273, 20999, 32905, 27748]; |
| fn check_read_ln(len : uint, s: &str, ivals: &[int]) { |
| do io::with_str_reader(s) |inp| { |
| let res : ~[char] = inp.read_chars(len); |
| if (len <= vec::len(ivals)) { |
| assert(vec::len(res) == len); |
| } |
| assert(vec::slice(ivals, 0u, vec::len(res)) == |
| vec::map(res, |x| *x as int)); |
| } |
| } |
| let mut i = 0; |
| while i < 8 { |
| check_read_ln(i, wide_test, ivals); |
| i += 1; |
| } |
| // check a long read for good measure |
| check_read_ln(128, wide_test, ivals); |
| } |
| |
| #[test] |
| fn test_readchar() { |
| do io::with_str_reader(~"生") |inp| { |
| let res : char = inp.read_char(); |
| assert(res as int == 29983); |
| } |
| } |
| |
| #[test] |
| fn test_readchar_empty() { |
| do io::with_str_reader(~"") |inp| { |
| let res : char = inp.read_char(); |
| assert(res as int == -1); |
| } |
| } |
| |
| #[test] |
| fn file_reader_not_exist() { |
| match io::file_reader(&Path("not a file")) { |
| result::Err(copy e) => { |
| assert e == ~"error opening not a file"; |
| } |
| result::Ok(_) => fail |
| } |
| } |
| |
| #[test] |
| fn file_writer_bad_name() { |
| match io::file_writer(&Path("?/?"), ~[]) { |
| result::Err(copy e) => { |
| assert str::starts_with(e, "error opening"); |
| } |
| result::Ok(_) => fail |
| } |
| } |
| |
| #[test] |
| fn buffered_file_writer_bad_name() { |
| match io::buffered_file_writer(&Path("?/?")) { |
| result::Err(copy e) => { |
| assert str::starts_with(e, "error opening"); |
| } |
| result::Ok(_) => fail |
| } |
| } |
| |
| #[test] |
| fn bytes_buffer_overwrite() { |
| let wr = BytesWriter(); |
| wr.write(~[0u8, 1u8, 2u8, 3u8]); |
| assert wr.buf.borrow(|buf| buf == ~[0u8, 1u8, 2u8, 3u8]); |
| wr.seek(-2, SeekCur); |
| wr.write(~[4u8, 5u8, 6u8, 7u8]); |
| assert wr.buf.borrow(|buf| buf == ~[0u8, 1u8, 4u8, 5u8, 6u8, 7u8]); |
| wr.seek(-2, SeekEnd); |
| wr.write(~[8u8]); |
| wr.seek(1, SeekSet); |
| wr.write(~[9u8]); |
| assert wr.buf.borrow(|buf| buf == ~[0u8, 9u8, 4u8, 5u8, 8u8, 7u8]); |
| } |
| } |
| |
| // |
| // Local Variables: |
| // mode: rust |
| // fill-column: 78; |
| // indent-tabs-mode: nil |
| // c-basic-offset: 4 |
| // buffer-file-coding-system: utf-8-unix |
| // End: |
| // |