From cf64324e19efd159fe0411f55a608f4f747e298d Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Thu, 30 May 2013 02:13:35 -0400
Subject: [PATCH 1/8] extra::term overhaul

---
 src/libextra/std.rc                      |   2 +
 src/libextra/term.rs                     |  73 +++---
 src/libextra/terminfo/parm.rs            | 195 ++++++++++++++
 src/libextra/terminfo/parser/compiled.rs | 320 +++++++++++++++++++++++
 src/libextra/terminfo/searcher.rs        |  87 ++++++
 src/libextra/terminfo/terminfo.rs        |  24 ++
 src/libextra/test.rs                     |  19 +-
 src/librustpkg/util.rs                   |  48 ++--
 src/libsyntax/diagnostic.rs              |  28 +-
 9 files changed, 715 insertions(+), 81 deletions(-)
 create mode 100644 src/libextra/terminfo/parm.rs
 create mode 100644 src/libextra/terminfo/parser/compiled.rs
 create mode 100644 src/libextra/terminfo/searcher.rs
 create mode 100644 src/libextra/terminfo/terminfo.rs

diff --git a/src/libextra/std.rc b/src/libextra/std.rc
index a81ab3005f6..4e9a547e141 100644
--- a/src/libextra/std.rc
+++ b/src/libextra/std.rc
@@ -118,6 +118,8 @@ pub mod flate;
 #[cfg(unicode)]
 mod unicode;
 
+#[path="terminfo/terminfo.rs"]
+pub mod terminfo;
 
 // Compiler support modules
 
diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index a76852dc661..14d29d22a92 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -1,4 +1,4 @@
-// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// 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.
 //
@@ -15,9 +15,13 @@
 use core::prelude::*;
 
 use core::io;
-use core::option;
 use core::os;
 
+use terminfo::*;
+use terminfo::searcher::open;
+use terminfo::parser::compiled::parse;
+use terminfo::parm::{expand, Number};
+
 // FIXME (#2807): Windows support.
 
 pub static color_black: u8 = 0u8;
@@ -41,41 +45,42 @@ pub static color_bright_white: u8 = 15u8;
 
 pub fn esc(writer: @io::Writer) { writer.write([0x1bu8, '[' as u8]); }
 
-/// Reset the foreground and background colors to default
-pub fn reset(writer: @io::Writer) {
-    esc(writer);
-    writer.write(['0' as u8, 'm' as u8]);
+pub struct Terminal {
+    color_supported: bool,
+    priv out: @io::Writer,
+    priv ti: ~TermInfo
 }
 
-/// Returns true if the terminal supports color
-pub fn color_supported() -> bool {
-    let supported_terms = ~[~"xterm-color", ~"xterm",
-                           ~"screen-bce", ~"xterm-256color"];
-    return match os::getenv("TERM") {
-          option::Some(ref env) => {
-            for supported_terms.each |term| {
-                if *term == *env { return true; }
-            }
-            false
-          }
-          option::None => false
-        };
-}
+pub impl Terminal {
+    pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
+        let term = os::getenv("TERM");
+        if term.is_none() {
+            return Err(~"TERM environment variable undefined");
+        }
 
-pub fn set_color(writer: @io::Writer, first_char: u8, color: u8) {
-    assert!((color < 16u8));
-    esc(writer);
-    let mut color = color;
-    if color >= 8u8 { writer.write(['1' as u8, ';' as u8]); color -= 8u8; }
-    writer.write([first_char, ('0' as u8) + color, 'm' as u8]);
-}
+        let entry = open(term.unwrap());
+        if entry.is_err() {
+            return Err(entry.get_err());
+        }
 
-/// Set the foreground color
-pub fn fg(writer: @io::Writer, color: u8) {
-    return set_color(writer, '3' as u8, color);
-}
+        let ti = parse(entry.get(), false);
+        if ti.is_err() {
+            return Err(entry.get_err());
+        }
 
-/// Set the background color
-pub fn bg(writer: @io::Writer, color: u8) {
-    return set_color(writer, '4' as u8, color);
+        let mut inf = ti.get();
+        let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16 && inf.strings.find(&~"setaf").is_some()
+            && inf.strings.find(&~"setab").is_some();
+
+        return Ok(Terminal {out: out, ti: inf, color_supported: cs});
+    }
+    fn fg(&self, color: u8) {
+        self.out.write(expand(*self.ti.strings.find(&~"setaf").unwrap(), [Number(color as int)], [], []));
+    }
+    fn bg(&self, color: u8) {
+        self.out.write(expand(*self.ti.strings.find(&~"setab").unwrap(), [Number(color as int)], [], []));
+    }
+    fn reset(&self) {
+        self.out.write(expand(*self.ti.strings.find(&~"op").unwrap(), [], [], []));
+    }
 }
diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
new file mode 100644
index 00000000000..c4cc16d058e
--- /dev/null
+++ b/src/libextra/terminfo/parm.rs
@@ -0,0 +1,195 @@
+// Copyright 2012 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Parameterized string expansion
+
+use core::prelude::*;
+use core::{char, int, vec};
+
+#[deriving(Eq)]
+enum States {
+    Nothing,
+    Percent,
+    SetVar,
+    GetVar,
+    PushParam,
+    CharConstant,
+    CharClose,
+    IntConstant,
+    IfCond,
+    IfBody
+}
+
+pub enum Param {
+    String(~str),
+    Char(char),
+    Number(int)
+}
+
+pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) -> ~[u8] {
+    assert!(cap.len() != 0, "expanding an empty capability makes no sense");
+    assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
+
+    assert!(sta.len() <= 26, "only 26 static vars are able to be used by capability strings");
+    assert!(dyn.len() <= 26, "only 26 dynamic vars are able to be used by capability strings");
+
+    let mut state = Nothing;
+    let mut i = 0;
+
+    // expanded cap will only rarely be smaller than the cap itself
+    let mut output = vec::with_capacity(cap.len());
+
+    let mut cur;
+
+    let mut stack: ~[Param] = ~[];
+
+    let mut intstate = ~[];
+
+    while i < cap.len() {
+        cur = cap[i] as char;
+        let mut old_state = state;
+        match state {
+            Nothing => {
+                if cur == '%' {
+                    state = Percent;
+                } else {
+                    output.push(cap[i]);
+                }
+            },
+            Percent => {
+                match cur {
+                    '%' => { output.push(cap[i]); state = Nothing },
+                    'c' => match stack.pop() {
+                        Char(c) => output.push(c as u8),
+                        _       => fail!("a non-char was used with %c")
+                    },
+                    's' => match stack.pop() {
+                        String(s) => output.push_all(s.to_bytes()),
+                        _         => fail!("a non-str was used with %s")
+                    },
+                    'd' => match stack.pop() {
+                        Number(x) => output.push_all(x.to_str().to_bytes()),
+                        _         => fail!("a non-number was used with %d")
+                    },
+                    'p' => state = PushParam,
+                    'P' => state = SetVar,
+                    'g' => state = GetVar,
+                    '\'' => state = CharConstant,
+                    '{' => state = IntConstant,
+                    'l' => match stack.pop() {
+                        String(s) => stack.push(Number(s.len() as int)),
+                        _         => fail!("a non-str was used with %l")
+                    },
+                    '+' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x + y)),
+                        (_, _) => fail!("non-numbers on stack with +")
+                    },
+                    '-' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x - y)),
+                        (_, _) => fail!("non-numbers on stack with -")
+                    },
+                    '*' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x * y)),
+                        (_, _) => fail!("non-numbers on stack with *")
+                    },
+                    '/' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x / y)),
+                        (_, _) => fail!("non-numbers on stack with /")
+                    },
+                    'm' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x % y)),
+                        (_, _) => fail!("non-numbers on stack with %")
+                    },
+                    '&' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x & y)),
+                        (_, _) => fail!("non-numbers on stack with &")
+                    },
+                    '|' => match (stack.pop(), stack.pop()) {
+                        (Number(x), Number(y)) => stack.push(Number(x | y)),
+                        (_, _) => fail!("non-numbers on stack with |")
+                    },
+                    'A' => fail!("logical operations unimplemented"),
+                    'O' => fail!("logical operations unimplemented"),
+                    '!' => fail!("logical operations unimplemented"),
+                    '~' => match stack.pop() {
+                        Number(x) => stack.push(Number(!x)),
+                        _         => fail!("non-number on stack with %~")
+                    },
+                    'i' => match (copy params[0], copy params[1]) {
+                        (Number(x), Number(y)) => {
+                            params[0] = Number(x + 1);
+                            params[1] = Number(y + 1);
+                        },
+                        (_, _) => fail!("first two params not numbers with %i")
+                    },
+                    '?' => state = fail!("if expressions unimplemented"),
+                    _ => fail!("unrecognized format option %c", cur)
+                }
+            },
+            PushParam => {
+                // params are 1-indexed
+                stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]);
+            },
+            SetVar => {
+                if cur >= 'A' && cur <= 'Z' {
+                    let idx = (cur as u8) - ('A' as u8);
+                    sta[idx] = stack.pop();
+                } else if cur >= 'a' && cur <= 'z' {
+                    let idx = (cur as u8) - ('a' as u8);
+                    dyn[idx] = stack.pop();
+                } else {
+                    fail!("bad variable name in %P");
+                }
+            },
+            GetVar => {
+                if cur >= 'A' && cur <= 'Z' {
+                    let idx = (cur as u8) - ('A' as u8);
+                    stack.push(copy sta[idx]);
+                } else if cur >= 'a' && cur <= 'z' {
+                    let idx = (cur as u8) - ('a' as u8);
+                    stack.push(copy dyn[idx]);
+                } else {
+                    fail!("bad variable name in %g");
+                }
+            },
+            CharConstant => {
+                stack.push(Char(cur));
+                state = CharClose;
+            },
+            CharClose => {
+                assert!(cur == '\'', "malformed character constant");
+            },
+            IntConstant => {
+                if cur == '}' {
+                    stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant")));
+                    state = Nothing;
+                }
+                intstate.push(cur as u8);
+                old_state = Nothing;
+            }
+            _ => fail!("unimplemented state")
+        }
+        if state == old_state {
+            state = Nothing;
+        }
+        i += 1;
+    }
+    output
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_basic_setabf() {
+        let s = bytes!("\\E[48;5;%p1%dm");
+        assert_eq!(expand(s, [Number(1)], [], []), bytes!("\\E[48;5;1m").to_owned());
+    }
+}
diff --git a/src/libextra/terminfo/parser/compiled.rs b/src/libextra/terminfo/parser/compiled.rs
new file mode 100644
index 00000000000..2d5979c2634
--- /dev/null
+++ b/src/libextra/terminfo/parser/compiled.rs
@@ -0,0 +1,320 @@
+/// ncurses-compatible compiled terminfo format parsing (term(5))
+///
+/// does *not* handle obsolete termcap capabilities!
+
+use core::prelude::*;
+
+use core::{vec, int, str};
+use core::io::Reader;
+use core::hashmap::HashMap;
+use super::super::TermInfo;
+
+// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
+
+pub static boolfnames: &'static[&'static str] = &'static["auto_left_margin", "auto_right_margin",
+    "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
+    "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
+    "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok",
+    "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff",
+    "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region",
+    "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch",
+    "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin",
+    "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling",
+    "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs",
+    "return_does_clr_eol"];
+
+pub static boolnames: &'static[&'static str] = &'static["bw", "am", "xsb", "xhp", "xenl", "eo",
+    "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
+    "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
+    "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
+
+pub static numfnames: &'static[&'static str] = &'static[ "columns", "init_tabs", "lines",
+    "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
+    "width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
+    "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity",
+    "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size",
+    "micro_line_size", "number_of_pins", "output_res_char", "output_res_line",
+    "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons",
+    "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay",
+    "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
+
+pub static numnames: &'static[&'static str] = &'static[ "cols", "it", "lines", "lm", "xmc", "pb",
+    "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
+    "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
+    "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
+
+pub static stringfnames: &'static[&'static str] = &'static[ "back_tab", "bell", "carriage_return",
+    "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
+    "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
+    "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right",
+    "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line",
+    "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode",
+    "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode",
+    "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode",
+    "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode",
+    "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode",
+    "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string",
+    "init_2string", "init_3string", "init_file", "insert_character", "insert_line",
+    "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl",
+    "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3",
+    "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il",
+    "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab",
+    "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3",
+    "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline",
+    "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index",
+    "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor",
+    "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char",
+    "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor",
+    "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab",
+    "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1",
+    "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm",
+    "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character",
+    "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close",
+    "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find",
+    "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options",
+    "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace",
+    "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel",
+    "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send",
+    "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft",
+    "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint",
+    "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend",
+    "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16",
+    "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24",
+    "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32",
+    "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40",
+    "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48",
+    "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56",
+    "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol",
+    "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock",
+    "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone",
+    "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1",
+    "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair",
+    "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground",
+    "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz",
+    "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality",
+    "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality",
+    "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode",
+    "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode",
+    "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode",
+    "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right",
+    "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro",
+    "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin",
+    "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin",
+    "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image",
+    "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr",
+    "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse",
+    "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init",
+    "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin",
+    "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return",
+    "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band",
+    "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode",
+    "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape",
+    "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode",
+    "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes",
+    "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs",
+    "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner",
+    "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline",
+    "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
+
+pub static stringnames: &'static[&'static str] = &'static[ "cbt", "_", "cr", "csr", "tbc", "clear",
+    "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
+    "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
+    "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc",
+    "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip",
+    "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
+    "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
+    "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
+    "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
+    "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
+    "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
+    "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp",
+    "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl",
+    "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_",
+    "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
+    "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+    "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf",
+    "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq",
+    "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm",
+    "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub",
+    "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd",
+    "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm",
+    "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb",
+    "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch",
+    "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm",
+    "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2",
+    "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
+    "box1"];
+
+pub fn parse(file: @Reader, longnames: bool) -> Result<~TermInfo, ~str> {
+    let bnames, snames, nnames;
+
+    if longnames {
+        bnames = boolfnames;
+        snames = stringfnames;
+        nnames = numfnames;
+    } else {
+        bnames = boolnames;
+        snames = stringnames;
+        nnames = numnames;
+    }
+
+    // Check magic number
+    let magic = file.read_le_u16();
+    if (magic != 0x011A) {
+        return Err(fmt!("invalid magic number: expected %x but found %x", 0x011A, magic as uint));
+    }
+
+    let names_bytes          = file.read_le_i16() as int;
+    let bools_bytes          = file.read_le_i16() as int;
+    let numbers_count        = file.read_le_i16() as int;
+    let string_offsets_count = file.read_le_i16() as int;
+    let string_table_bytes   = file.read_le_i16() as int;
+
+    assert!(names_bytes          > 0);
+
+    debug!("names_bytes = %?", names_bytes);
+    debug!("bools_bytes = %?", bools_bytes);
+    debug!("numbers_count = %?", numbers_count);
+    debug!("string_offsets_count = %?", string_offsets_count);
+    debug!("string_table_bytes = %?", string_table_bytes);
+
+    if (bools_bytes as uint) > boolnames.len() {
+        error!("expected bools_bytes to be less than %? but found %?", boolnames.len(),
+               bools_bytes);
+        return Err(~"incompatible file: more booleans than expected");
+    }
+
+    if (numbers_count as uint) > numnames.len() {
+        error!("expected numbers_count to be less than %? but found %?", numnames.len(),
+               numbers_count);
+        return Err(~"incompatible file: more numbers than expected");
+    }
+
+    if (string_offsets_count as uint) > stringnames.len() {
+        error!("expected string_offsets_count to be less than %? but found %?", stringnames.len(),
+               string_offsets_count);
+        return Err(~"incompatible file: more string offsets than expected");
+    }
+
+    let mut term_names: ~[~str] = vec::with_capacity(2);
+    let names_str = str::from_bytes(file.read_bytes(names_bytes as uint - 1)); // don't read NUL
+    for names_str.each_split_char('|') |s| {
+        term_names.push(s.to_owned());
+    }
+
+    file.read_byte(); // consume NUL
+
+    debug!("term names: %?", term_names);
+
+    let mut bools_map = HashMap::new();
+    if bools_bytes != 0 {
+        for int::range(0, bools_bytes) |i| {
+            let b = file.read_byte();
+            if b < 0 {
+                error!("EOF reading bools after %? entries", i);
+                return Err(~"error: expected more bools but hit EOF");
+            } else if b == 1 {
+                debug!("%s set", bnames[i]);
+                bools_map.insert(bnames[i].to_owned(), true);
+            }
+        }
+    }
+
+    debug!("bools: %?", bools_map);
+
+    if (bools_bytes + names_bytes) % 2 == 1 {
+        debug!("adjusting for padding between bools and numbers");
+        file.read_byte(); // compensate for padding
+    }
+
+    let mut numbers_map = HashMap::new();
+    if numbers_count != 0 {
+        for int::range(0, numbers_count) |i| {
+            let n = file.read_le_u16();
+            if n != 0xFFFF {
+                debug!("%s#%?", nnames[i], n);
+                numbers_map.insert(nnames[i].to_owned(), n);
+            }
+        }
+    }
+
+    debug!("numbers: %?", numbers_map);
+
+    let mut string_map = HashMap::new();
+
+    if string_offsets_count != 0 {
+        let mut string_offsets = vec::with_capacity(10);
+        for int::range(0, string_offsets_count) |_i| {
+            string_offsets.push(file.read_le_u16());
+        }
+
+        debug!("offsets: %?", string_offsets);
+
+        let string_table = file.read_bytes(string_table_bytes as uint);
+
+        if string_table.len() != string_table_bytes as uint {
+            error!("EOF reading string table after %? bytes, wanted %?", string_table.len(),
+                   string_table_bytes);
+            return Err(~"error: hit EOF before end of string table");
+        }
+
+        for string_offsets.eachi |i, v| {
+            let offset = *v;
+            if offset == 0xFFFF { // non-entry
+                loop;
+            }
+
+            let name = if snames[i] == "_" {
+                stringfnames[i]
+            } else {
+                snames[i]
+            };
+
+            if offset == 0xFFFE {
+                // undocumented: FFFE indicates cap@, which means the capability is not present
+                // unsure if the handling for this is correct
+                string_map.insert(name.to_owned(), ~[]);
+                loop;
+            }
+
+
+            // Find the offset of the NUL we want to go to
+            let nulpos = vec::position_between(string_table, offset as uint, string_table_bytes as uint,
+                                               |&b| b == 0);
+            match nulpos {
+                Some(x) => {
+                    string_map.insert(name.to_owned(), string_table.slice(offset as uint, x).to_owned())
+                },
+                None => {
+                    return Err(~"invalid file: missing NUL in string_table");
+                }
+            };
+        }
+    }
+
+    // And that's all there is to it
+    Ok(~TermInfo {names: term_names, bools: bools_map, numbers: numbers_map, strings: string_map })
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use p = std::path::PosixPath;
+
+    #[test]
+    fn test_veclens() {
+        assert_eq!(boolfnames.len(), boolnames.len());
+        assert_eq!(numfnames.len(), numnames.len());
+        assert_eq!(stringfnames.len(), stringnames.len());
+    }
+
+    #[test]
+    fn test_parse() {
+        parse(io::file_reader(&p("/usr/share/terminfo/r/rxvt-256color")).unwrap(), false);
+    }
+}
diff --git a/src/libextra/terminfo/searcher.rs b/src/libextra/terminfo/searcher.rs
new file mode 100644
index 00000000000..f440ce7c9eb
--- /dev/null
+++ b/src/libextra/terminfo/searcher.rs
@@ -0,0 +1,87 @@
+// Copyright 2012 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/// Implement ncurses-compatible database discovery
+/// Does not support hashed database, only filesystem!
+
+use core::prelude::*;
+use core::{os, str, vec};
+use core::os::getenv;
+use core::io::{file_reader, Reader};
+use path = core::path::PosixPath;
+
+/// Return path to database entry for `term`
+pub fn get_dbpath_for_term(term: &str) -> Option<~path> {
+    if term.len() == 0 {
+        return None;
+    }
+
+    let homedir = os::homedir();
+
+    let mut dirs_to_search = ~[];
+    let first_char = term.substr(0, 1);
+
+    // Find search directory
+    match getenv("TERMINFO") {
+        Some(dir) => dirs_to_search.push(path(dir)),
+        None => {
+            if homedir.is_some() {
+                dirs_to_search.push(homedir.unwrap().push(".terminfo")); // ncurses compatability
+            }
+            match getenv("TERMINFO_DIRS") {
+                Some(dirs) => for str::each_split_char(dirs, ':') |i| {
+                    if i == "" {
+                        dirs_to_search.push(path("/usr/share/terminfo"));
+                    } else {
+                        dirs_to_search.push(path(i.to_owned()));
+                    }
+                },
+                // Found nothing, use the default path
+                None => dirs_to_search.push(path("/usr/share/terminfo"))
+            }
+        }
+    };
+
+    // Look for the terminal in all of the search directories
+    for dirs_to_search.each |p| {
+        let newp = ~p.push_many(&[first_char.to_owned(), term.to_owned()]);
+        if os::path_exists(p) && os::path_exists(newp) {
+            return Some(newp);
+        }
+    }
+    None
+}
+
+/// Return open file for `term`
+pub fn open(term: &str) -> Result<@Reader, ~str> {
+    match get_dbpath_for_term(term) {
+        Some(x) => file_reader(x),
+        None => Err(fmt!("could not find terminfo entry for %s", term))
+    }
+}
+
+#[test]
+fn test_get_dbpath_for_term() {
+    // woefully inadequate test coverage
+    use std::os::{setenv, unsetenv};
+    fn x(t: &str) -> ~str { get_dbpath_for_term(t).expect("no terminfo entry found").to_str() };
+    assert!(x("screen") == ~"/usr/share/terminfo/s/screen");
+    assert!(get_dbpath_for_term("") == None);
+    setenv("TERMINFO_DIRS", ":");
+    assert!(x("screen") == ~"/usr/share/terminfo/s/screen");
+    unsetenv("TERMINFO_DIRS");
+}
+
+#[test]
+fn test_open() {
+    open("screen");
+    let t = open("nonexistent terminal that hopefully does not exist");
+    assert!(t.is_err());
+}
diff --git a/src/libextra/terminfo/terminfo.rs b/src/libextra/terminfo/terminfo.rs
new file mode 100644
index 00000000000..1c7f107e4fb
--- /dev/null
+++ b/src/libextra/terminfo/terminfo.rs
@@ -0,0 +1,24 @@
+// Copyright 2012 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use core::hashmap::HashMap;
+
+pub struct TermInfo {
+    names: ~[~str],
+    bools: HashMap<~str, bool>,
+    numbers: HashMap<~str, u16>,
+    strings: HashMap<~str, ~[u8]>
+}
+
+pub mod searcher;
+pub mod parser {
+    pub mod compiled;
+}
+pub mod parm;
diff --git a/src/libextra/test.rs b/src/libextra/test.rs
index 141700bd0b3..e6e1d912bb7 100644
--- a/src/libextra/test.rs
+++ b/src/libextra/test.rs
@@ -210,7 +210,6 @@ struct ConsoleTestState {
 // A simple console test runner
 pub fn run_tests_console(opts: &TestOpts,
                          tests: ~[TestDescAndFn]) -> bool {
-
     fn callback(event: &TestEvent, st: &mut ConsoleTestState) {
         debug!("callback(event=%?)", event);
         match copy *event {
@@ -347,12 +346,18 @@ pub fn run_tests_console(opts: &TestOpts,
                     word: &str,
                     color: u8,
                     use_color: bool) {
-        if use_color && term::color_supported() {
-            term::fg(out, color);
-        }
-        out.write_str(word);
-        if use_color && term::color_supported() {
-            term::reset(out);
+        let t = term::Terminal::new(out);
+        match t {
+            Ok(term)  => {
+                if use_color && term.color_supported {
+                    term.fg(color);
+                }
+                out.write_str(word);
+                if use_color && term.color_supported {
+                    term.reset();
+                }
+            },
+            Err(_) => out.write_str(word)
         }
     }
 }
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 8019b3b8afb..f6f8c6f6754 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -277,43 +277,31 @@ pub fn need_dir(s: &Path) {
     }
 }
 
-pub fn note(msg: ~str) {
-    let out = io::stdout();
-
-    if term::color_supported() {
-        term::fg(out, term::color_green);
-        out.write_str("note: ");
-        term::reset(out);
-        out.write_line(msg);
-    } else {
-        out.write_line(~"note: " + msg);
+fn pretty_message<'a>(msg: &'a str, prefix: &'a str, color: u8, out: @io::Writer) {
+    let term = term::Terminal::new(out);
+    match term {
+        Ok(ref t) if t.color_supported => {
+            t.fg(color);
+            out.write_str(prefix);
+            t.reset();
+        },
+        _ => {
+            out.write_str(prefix);
+        }
     }
+    out.write_line(msg);
+}
+
+pub fn note(msg: ~str) {
+    pretty_message(msg, "note: ", term::color_green, io::stdout())
 }
 
 pub fn warn(msg: ~str) {
-    let out = io::stdout();
-
-    if term::color_supported() {
-        term::fg(out, term::color_yellow);
-        out.write_str("warning: ");
-        term::reset(out);
-        out.write_line(msg);
-    } else {
-        out.write_line(~"warning: " + msg);
-    }
+    pretty_message(msg, "warning: ", term::color_yellow, io::stdout())
 }
 
 pub fn error(msg: ~str) {
-    let out = io::stdout();
-
-    if term::color_supported() {
-        term::fg(out, term::color_red);
-        out.write_str("error: ");
-        term::reset(out);
-        out.write_line(msg);
-    } else {
-        out.write_line(~"error: " + msg);
-    }
+    pretty_message(msg, "error: ", term::color_red, io::stdout())
 }
 
 pub fn hash(data: ~str) -> ~str {
diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs
index 76ede098ac3..094082f41e7 100644
--- a/src/libsyntax/diagnostic.rs
+++ b/src/libsyntax/diagnostic.rs
@@ -191,19 +191,27 @@ fn diagnosticcolor(lvl: level) -> u8 {
 }
 
 fn print_diagnostic(topic: &str, lvl: level, msg: &str) {
-    let use_color = term::color_supported() &&
-        io::stderr().get_type() == io::Screen;
+    let term = term::Terminal::new(io::stderr());
+
+    let stderr = io::stderr();
+
     if !topic.is_empty() {
-        io::stderr().write_str(fmt!("%s ", topic));
+        stderr.write_str(fmt!("%s ", topic));
     }
-    if use_color {
-        term::fg(io::stderr(), diagnosticcolor(lvl));
+
+    match term {
+        Ok(t) => {
+            if stderr.get_type() == io::Screen {
+                t.fg(diagnosticcolor(lvl));
+                stderr.write_str(fmt!("%s: ", diagnosticstr(lvl)));
+                t.reset();
+                stderr.write_str(fmt!("%s\n", msg));
+            }
+        }
+        Err(_) => {
+            stderr.write_str(fmt!("%s: %s\n", diagnosticstr(lvl), msg));
+        }
     }
-    io::stderr().write_str(fmt!("%s:", diagnosticstr(lvl)));
-    if use_color {
-        term::reset(io::stderr());
-    }
-    io::stderr().write_str(fmt!(" %s\n", msg));
 }
 
 pub fn collect(messages: @mut ~[~str])

From 72811669619c593249de876003ee2205710a8fcf Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Thu, 30 May 2013 02:19:03 -0400
Subject: [PATCH 2/8] Use find_equiv in term

---
 src/libextra/term.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index 14d29d22a92..eb1a0897e33 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -70,17 +70,17 @@ pub impl Terminal {
 
         let mut inf = ti.get();
         let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16 && inf.strings.find(&~"setaf").is_some()
-            && inf.strings.find(&~"setab").is_some();
+            && inf.strings.find_equiv(&("setab")).is_some();
 
         return Ok(Terminal {out: out, ti: inf, color_supported: cs});
     }
     fn fg(&self, color: u8) {
-        self.out.write(expand(*self.ti.strings.find(&~"setaf").unwrap(), [Number(color as int)], [], []));
+        self.out.write(expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(), [Number(color as int)], [], []));
     }
     fn bg(&self, color: u8) {
-        self.out.write(expand(*self.ti.strings.find(&~"setab").unwrap(), [Number(color as int)], [], []));
+        self.out.write(expand(*self.ti.strings.find_equiv(&("setab")).unwrap(), [Number(color as int)], [], []));
     }
     fn reset(&self) {
-        self.out.write(expand(*self.ti.strings.find(&~"op").unwrap(), [], [], []));
+        self.out.write(expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []));
     }
 }

From 1f27c6306d9f376f9b629a98f8a57c2e43e59b41 Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Thu, 30 May 2013 02:26:33 -0400
Subject: [PATCH 3/8] rustpkg borrowed pointers

---
 src/librustpkg/util.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index f6f8c6f6754..213ac4f22e2 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -292,15 +292,15 @@ fn pretty_message<'a>(msg: &'a str, prefix: &'a str, color: u8, out: @io::Writer
     out.write_line(msg);
 }
 
-pub fn note(msg: ~str) {
+pub fn note(msg: &str) {
     pretty_message(msg, "note: ", term::color_green, io::stdout())
 }
 
-pub fn warn(msg: ~str) {
+pub fn warn(msg: &str) {
     pretty_message(msg, "warning: ", term::color_yellow, io::stdout())
 }
 
-pub fn error(msg: ~str) {
+pub fn error(msg: &str) {
     pretty_message(msg, "error: ", term::color_red, io::stdout())
 }
 

From 100ee8409789edb290a1d3309f70286c6a799a7c Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Thu, 30 May 2013 13:21:41 -0400
Subject: [PATCH 4/8] Only output colors if colors are supported (removes
 burden from caller)

---
 src/libextra/term.rs | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index eb1a0897e33..691a6b4dc62 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -43,8 +43,6 @@ pub static color_bright_magenta: u8 = 13u8;
 pub static color_bright_cyan: u8 = 14u8;
 pub static color_bright_white: u8 = 15u8;
 
-pub fn esc(writer: @io::Writer) { writer.write([0x1bu8, '[' as u8]); }
-
 pub struct Terminal {
     color_supported: bool,
     priv out: @io::Writer,
@@ -75,12 +73,20 @@ pub impl Terminal {
         return Ok(Terminal {out: out, ti: inf, color_supported: cs});
     }
     fn fg(&self, color: u8) {
-        self.out.write(expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(), [Number(color as int)], [], []));
+        if self.color_supported {
+            self.out.write(expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(), 
+                                  [Number(color as int)], [], []));
+        }
     }
     fn bg(&self, color: u8) {
-        self.out.write(expand(*self.ti.strings.find_equiv(&("setab")).unwrap(), [Number(color as int)], [], []));
+        if self.color_supported {
+            self.out.write(expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
+                                  [Number(color as int)], [], []));
+        }
     }
     fn reset(&self) {
-        self.out.write(expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []));
+        if self.color_supported {
+            self.out.write(expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []));
+        }
     }
 }

From 5311d590230428aa5f2473d9dc6355ed943f731d Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Thu, 30 May 2013 19:14:40 -0400
Subject: [PATCH 5/8] extra::term: better error handling and win32 compat

---
 src/libextra/term.rs                     | 49 +++++++++++++++++--
 src/libextra/terminfo/parm.rs            | 60 +++++++++++++++---------
 src/libextra/terminfo/parser/compiled.rs |  3 +-
 src/libextra/terminfo/searcher.rs        |  4 +-
 src/libextra/terminfo/terminfo.rs        |  5 ++
 src/librustpkg/util.rs                   |  2 +-
 src/libsyntax/diagnostic.rs              | 18 +++----
 7 files changed, 99 insertions(+), 42 deletions(-)

diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index 691a6b4dc62..26a9e568e61 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -43,12 +43,20 @@ pub static color_bright_magenta: u8 = 13u8;
 pub static color_bright_cyan: u8 = 14u8;
 pub static color_bright_white: u8 = 15u8;
 
+#[cfg(not(target_os = "win32"))]
 pub struct Terminal {
     color_supported: bool,
     priv out: @io::Writer,
     priv ti: ~TermInfo
 }
 
+#[cfg(target_os = "win32")]
+pub struct Terminal {
+    color_supported: bool,
+    priv out: @io::Writer,
+}
+
+#[cfg(not(target_os = "win32"))]
 pub impl Terminal {
     pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
         let term = os::getenv("TERM");
@@ -74,19 +82,50 @@ pub impl Terminal {
     }
     fn fg(&self, color: u8) {
         if self.color_supported {
-            self.out.write(expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(), 
-                                  [Number(color as int)], [], []));
+            let s = expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(),
+                           [Number(color as int)], [], []);
+            if s.is_ok() {
+                self.out.write(s.get());
+            } else {
+                warn!(s.get_err());
+            }
         }
     }
     fn bg(&self, color: u8) {
         if self.color_supported {
-            self.out.write(expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
-                                  [Number(color as int)], [], []));
+            let s = expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
+                           [Number(color as int)], [], []);
+            if s.is_ok() {
+                self.out.write(s.get());
+            } else {
+                warn!(s.get_err());
+            }
         }
     }
     fn reset(&self) {
         if self.color_supported {
-            self.out.write(expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []));
+            let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []);
+            if s.is_ok() {
+                self.out.write(s.get());
+            } else {
+                warn!(s.get_err());
+            }
         }
     }
 }
+
+#[cfg(target_os = "win32")]
+pub impl Terminal {
+    pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
+        return Ok(Terminal {out: out, color_supported: false});
+    }
+
+    fn fg(&self, color: u8) {
+    }
+
+    fn bg(&self, color: u8) {
+    }
+
+    fn reset(&self) {
+    }
+}
diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
index c4cc16d058e..aee4b449449 100644
--- a/src/libextra/terminfo/parm.rs
+++ b/src/libextra/terminfo/parm.rs
@@ -27,13 +27,27 @@ enum States {
     IfBody
 }
 
+/// Types of parameters a capability can use
 pub enum Param {
     String(~str),
     Char(char),
     Number(int)
 }
 
-pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) -> ~[u8] {
+/**
+  Expand a parameterized capability
+
+  # Arguments
+  * `cap`    - string to expand
+  * `params` - vector of params for %p1 etc
+  * `sta`    - vector of params corresponding to static variables
+  * `dyn`    - vector of params corresponding to stativ variables
+
+  To be compatible with ncurses, `sta` and `dyn` should be the same between calls to `expand` for
+  multiple capabilities for the same terminal.
+  */
+pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) 
+    -> Result<~[u8], ~str> {
     assert!(cap.len() != 0, "expanding an empty capability makes no sense");
     assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
 
@@ -68,15 +82,15 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
                     '%' => { output.push(cap[i]); state = Nothing },
                     'c' => match stack.pop() {
                         Char(c) => output.push(c as u8),
-                        _       => fail!("a non-char was used with %c")
+                        _       => return Err(~"a non-char was used with %c")
                     },
                     's' => match stack.pop() {
                         String(s) => output.push_all(s.to_bytes()),
-                        _         => fail!("a non-str was used with %s")
+                        _         => return Err(~"a non-str was used with %s")
                     },
                     'd' => match stack.pop() {
                         Number(x) => output.push_all(x.to_str().to_bytes()),
-                        _         => fail!("a non-number was used with %d")
+                        _         => return Err(~"a non-number was used with %d")
                     },
                     'p' => state = PushParam,
                     'P' => state = SetVar,
@@ -85,52 +99,52 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
                     '{' => state = IntConstant,
                     'l' => match stack.pop() {
                         String(s) => stack.push(Number(s.len() as int)),
-                        _         => fail!("a non-str was used with %l")
+                        _         => return Err(~"a non-str was used with %l")
                     },
                     '+' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x + y)),
-                        (_, _) => fail!("non-numbers on stack with +")
+                        (_, _) => return Err(~"non-numbers on stack with +")
                     },
                     '-' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x - y)),
-                        (_, _) => fail!("non-numbers on stack with -")
+                        (_, _) => return Err(~"non-numbers on stack with -")
                     },
                     '*' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x * y)),
-                        (_, _) => fail!("non-numbers on stack with *")
+                        (_, _) => return Err(~"non-numbers on stack with *")
                     },
                     '/' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x / y)),
-                        (_, _) => fail!("non-numbers on stack with /")
+                        (_, _) => return Err(~"non-numbers on stack with /")
                     },
                     'm' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x % y)),
-                        (_, _) => fail!("non-numbers on stack with %")
+                        (_, _) => return Err(~"non-numbers on stack with %")
                     },
                     '&' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x & y)),
-                        (_, _) => fail!("non-numbers on stack with &")
+                        (_, _) => return Err(~"non-numbers on stack with &")
                     },
                     '|' => match (stack.pop(), stack.pop()) {
                         (Number(x), Number(y)) => stack.push(Number(x | y)),
-                        (_, _) => fail!("non-numbers on stack with |")
+                        (_, _) => return Err(~"non-numbers on stack with |")
                     },
-                    'A' => fail!("logical operations unimplemented"),
-                    'O' => fail!("logical operations unimplemented"),
-                    '!' => fail!("logical operations unimplemented"),
+                    'A' => return Err(~"logical operations unimplemented"),
+                    'O' => return Err(~"logical operations unimplemented"),
+                    '!' => return Err(~"logical operations unimplemented"),
                     '~' => match stack.pop() {
                         Number(x) => stack.push(Number(!x)),
-                        _         => fail!("non-number on stack with %~")
+                        _         => return Err(~"non-number on stack with %~")
                     },
                     'i' => match (copy params[0], copy params[1]) {
                         (Number(x), Number(y)) => {
                             params[0] = Number(x + 1);
                             params[1] = Number(y + 1);
                         },
-                        (_, _) => fail!("first two params not numbers with %i")
+                        (_, _) => return Err(~"first two params not numbers with %i")
                     },
-                    '?' => state = fail!("if expressions unimplemented"),
-                    _ => fail!("unrecognized format option %c", cur)
+                    '?' => state = return Err(fmt!("if expressions unimplemented (%?)", cap)),
+                    _ => return Err(fmt!("unrecognized format option %c", cur))
                 }
             },
             PushParam => {
@@ -145,7 +159,7 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
                     let idx = (cur as u8) - ('a' as u8);
                     dyn[idx] = stack.pop();
                 } else {
-                    fail!("bad variable name in %P");
+                    return Err(~"bad variable name in %P");
                 }
             },
             GetVar => {
@@ -156,7 +170,7 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
                     let idx = (cur as u8) - ('a' as u8);
                     stack.push(copy dyn[idx]);
                 } else {
-                    fail!("bad variable name in %g");
+                    return Err(~"bad variable name in %g");
                 }
             },
             CharConstant => {
@@ -174,14 +188,14 @@ pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Pa
                 intstate.push(cur as u8);
                 old_state = Nothing;
             }
-            _ => fail!("unimplemented state")
+            _ => return Err(~"unimplemented state")
         }
         if state == old_state {
             state = Nothing;
         }
         i += 1;
     }
-    output
+    Ok(output)
 }
 
 #[cfg(test)]
diff --git a/src/libextra/terminfo/parser/compiled.rs b/src/libextra/terminfo/parser/compiled.rs
index 2d5979c2634..e63ec1ab5f9 100644
--- a/src/libextra/terminfo/parser/compiled.rs
+++ b/src/libextra/terminfo/parser/compiled.rs
@@ -1,6 +1,4 @@
 /// ncurses-compatible compiled terminfo format parsing (term(5))
-///
-/// does *not* handle obsolete termcap capabilities!
 
 use core::prelude::*;
 
@@ -150,6 +148,7 @@ pub static stringnames: &'static[&'static str] = &'static[ "cbt", "_", "cr", "cs
     "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
     "box1"];
 
+/// Parse a compiled terminfo entry, using long capability names if `longnames` is true
 pub fn parse(file: @Reader, longnames: bool) -> Result<~TermInfo, ~str> {
     let bnames, snames, nnames;
 
diff --git a/src/libextra/terminfo/searcher.rs b/src/libextra/terminfo/searcher.rs
index f440ce7c9eb..c2ff8a46ed4 100644
--- a/src/libextra/terminfo/searcher.rs
+++ b/src/libextra/terminfo/searcher.rs
@@ -12,10 +12,10 @@
 /// Does not support hashed database, only filesystem!
 
 use core::prelude::*;
-use core::{os, str, vec};
+use core::{os, str};
 use core::os::getenv;
 use core::io::{file_reader, Reader};
-use path = core::path::PosixPath;
+use path = core::path::Path;
 
 /// Return path to database entry for `term`
 pub fn get_dbpath_for_term(term: &str) -> Option<~path> {
diff --git a/src/libextra/terminfo/terminfo.rs b/src/libextra/terminfo/terminfo.rs
index 1c7f107e4fb..141a5b6bf7f 100644
--- a/src/libextra/terminfo/terminfo.rs
+++ b/src/libextra/terminfo/terminfo.rs
@@ -10,10 +10,15 @@
 
 use core::hashmap::HashMap;
 
+/// A parsed terminfo entry.
 pub struct TermInfo {
+    /// Names for the terminal
     names: ~[~str],
+    /// Map of capability name to boolean value
     bools: HashMap<~str, bool>,
+    /// Map of capability name to numeric value
     numbers: HashMap<~str, u16>,
+    /// Map of capability name to raw (unexpanded) string
     strings: HashMap<~str, ~[u8]>
 }
 
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 213ac4f22e2..8071546136f 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -280,7 +280,7 @@ pub fn need_dir(s: &Path) {
 fn pretty_message<'a>(msg: &'a str, prefix: &'a str, color: u8, out: @io::Writer) {
     let term = term::Terminal::new(out);
     match term {
-        Ok(ref t) if t.color_supported => {
+        Ok(ref t) => {
             t.fg(color);
             out.write_str(prefix);
             t.reset();
diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs
index 094082f41e7..fa4a74b8954 100644
--- a/src/libsyntax/diagnostic.rs
+++ b/src/libsyntax/diagnostic.rs
@@ -191,7 +191,7 @@ fn diagnosticcolor(lvl: level) -> u8 {
 }
 
 fn print_diagnostic(topic: &str, lvl: level, msg: &str) {
-    let term = term::Terminal::new(io::stderr());
+    let t = term::Terminal::new(io::stderr());
 
     let stderr = io::stderr();
 
@@ -199,18 +199,18 @@ fn print_diagnostic(topic: &str, lvl: level, msg: &str) {
         stderr.write_str(fmt!("%s ", topic));
     }
 
-    match term {
-        Ok(t) => {
+    match t {
+        Ok(term) => {
             if stderr.get_type() == io::Screen {
-                t.fg(diagnosticcolor(lvl));
+                term.fg(diagnosticcolor(lvl));
                 stderr.write_str(fmt!("%s: ", diagnosticstr(lvl)));
-                t.reset();
+                term.reset();
                 stderr.write_str(fmt!("%s\n", msg));
+            } else {
+                stderr.write_str(fmt!("%s: %s\n", diagnosticstr(lvl), msg));
             }
-        }
-        Err(_) => {
-            stderr.write_str(fmt!("%s: %s\n", diagnosticstr(lvl), msg));
-        }
+        },
+        _ => stderr.write_str(fmt!("%s: %s\n", diagnosticstr(lvl), msg))
     }
 }
 

From 11f31b96848c7d04f79e6aaa2c789857bb224526 Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Fri, 31 May 2013 13:37:58 -0400
Subject: [PATCH 6/8] Fix formatting for tidy

---
 src/libextra/term.rs                     |  3 ++-
 src/libextra/terminfo/parm.rs            |  2 +-
 src/libextra/terminfo/parser/compiled.rs | 22 +++++++++++++++++-----
 3 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/src/libextra/term.rs b/src/libextra/term.rs
index 26a9e568e61..e74a0f4e18e 100644
--- a/src/libextra/term.rs
+++ b/src/libextra/term.rs
@@ -75,7 +75,8 @@ pub impl Terminal {
         }
 
         let mut inf = ti.get();
-        let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16 && inf.strings.find(&~"setaf").is_some()
+        let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16
+            && inf.strings.find(&~"setaf").is_some()
             && inf.strings.find_equiv(&("setab")).is_some();
 
         return Ok(Terminal {out: out, ti: inf, color_supported: cs});
diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
index aee4b449449..fd051172792 100644
--- a/src/libextra/terminfo/parm.rs
+++ b/src/libextra/terminfo/parm.rs
@@ -46,7 +46,7 @@ pub enum Param {
   To be compatible with ncurses, `sta` and `dyn` should be the same between calls to `expand` for
   multiple capabilities for the same terminal.
   */
-pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) 
+pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param])
     -> Result<~[u8], ~str> {
     assert!(cap.len() != 0, "expanding an empty capability makes no sense");
     assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
diff --git a/src/libextra/terminfo/parser/compiled.rs b/src/libextra/terminfo/parser/compiled.rs
index e63ec1ab5f9..03fbfc2dbff 100644
--- a/src/libextra/terminfo/parser/compiled.rs
+++ b/src/libextra/terminfo/parser/compiled.rs
@@ -1,3 +1,13 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
 /// ncurses-compatible compiled terminfo format parsing (term(5))
 
 use core::prelude::*;
@@ -283,11 +293,12 @@ pub fn parse(file: @Reader, longnames: bool) -> Result<~TermInfo, ~str> {
 
 
             // Find the offset of the NUL we want to go to
-            let nulpos = vec::position_between(string_table, offset as uint, string_table_bytes as uint,
-                                               |&b| b == 0);
+            let nulpos = vec::position_between(string_table, offset as uint,
+                                               string_table_bytes as uint, |&b| b == 0);
             match nulpos {
                 Some(x) => {
-                    string_map.insert(name.to_owned(), string_table.slice(offset as uint, x).to_owned())
+                    string_map.insert(name.to_owned(),
+                                      string_table.slice(offset as uint, x).to_owned())
                 },
                 None => {
                     return Err(~"invalid file: missing NUL in string_table");
@@ -303,7 +314,7 @@ pub fn parse(file: @Reader, longnames: bool) -> Result<~TermInfo, ~str> {
 #[cfg(test)]
 mod test {
     use super::*;
-    use p = std::path::PosixPath;
+    use p = std::path::Path;
 
     #[test]
     fn test_veclens() {
@@ -314,6 +325,7 @@ mod test {
 
     #[test]
     fn test_parse() {
-        parse(io::file_reader(&p("/usr/share/terminfo/r/rxvt-256color")).unwrap(), false);
+        // FIXME #6870: Distribute a compiled file in src/tests and test there
+        // parse(io::file_reader(&p("/usr/share/terminfo/r/rxvt-256color")).unwrap(), false);
     }
 }

From 023861cbd1c817fd092029141bef96639df8c6d2 Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Sat, 1 Jun 2013 13:24:58 -0400
Subject: [PATCH 7/8] test fixes

---
 src/libextra/terminfo/parm.rs            | 2 +-
 src/libextra/terminfo/parser/compiled.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
index fd051172792..4eb48f60a99 100644
--- a/src/libextra/terminfo/parm.rs
+++ b/src/libextra/terminfo/parm.rs
@@ -204,6 +204,6 @@ mod test {
     #[test]
     fn test_basic_setabf() {
         let s = bytes!("\\E[48;5;%p1%dm");
-        assert_eq!(expand(s, [Number(1)], [], []), bytes!("\\E[48;5;1m").to_owned());
+        assert_eq!(expand(s, [Number(1)], [], []).unwrap(), bytes!("\\E[48;5;1m").to_owned());
     }
 }
diff --git a/src/libextra/terminfo/parser/compiled.rs b/src/libextra/terminfo/parser/compiled.rs
index 03fbfc2dbff..9ff46bfb253 100644
--- a/src/libextra/terminfo/parser/compiled.rs
+++ b/src/libextra/terminfo/parser/compiled.rs
@@ -314,7 +314,7 @@ pub fn parse(file: @Reader, longnames: bool) -> Result<~TermInfo, ~str> {
 #[cfg(test)]
 mod test {
     use super::*;
-    use p = std::path::Path;
+    use p = core::path::Path;
 
     #[test]
     fn test_veclens() {

From ae5f3de5f00368cd8c5e62a472ab0062115d97df Mon Sep 17 00:00:00 2001
From: Corey Richardson <corey@octayn.net>
Date: Mon, 3 Jun 2013 16:05:46 -0400
Subject: [PATCH 8/8] Ignore tests that cannot pass on buildbot

---
 src/libextra/terminfo/parser/compiled.rs | 1 +
 src/libextra/terminfo/searcher.rs        | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/src/libextra/terminfo/parser/compiled.rs b/src/libextra/terminfo/parser/compiled.rs
index 9ff46bfb253..10b6d386085 100644
--- a/src/libextra/terminfo/parser/compiled.rs
+++ b/src/libextra/terminfo/parser/compiled.rs
@@ -324,6 +324,7 @@ mod test {
     }
 
     #[test]
+    #[ignore(reason = "no ncurses on buildbots, needs a bundled terminfo file to test against")]
     fn test_parse() {
         // FIXME #6870: Distribute a compiled file in src/tests and test there
         // parse(io::file_reader(&p("/usr/share/terminfo/r/rxvt-256color")).unwrap(), false);
diff --git a/src/libextra/terminfo/searcher.rs b/src/libextra/terminfo/searcher.rs
index c2ff8a46ed4..d6577cf3b94 100644
--- a/src/libextra/terminfo/searcher.rs
+++ b/src/libextra/terminfo/searcher.rs
@@ -68,6 +68,7 @@ pub fn open(term: &str) -> Result<@Reader, ~str> {
 }
 
 #[test]
+#[ignore(reason = "buildbots don't have ncurses installed and I can't mock everything I need")]
 fn test_get_dbpath_for_term() {
     // woefully inadequate test coverage
     use std::os::{setenv, unsetenv};
@@ -80,6 +81,7 @@ fn test_get_dbpath_for_term() {
 }
 
 #[test]
+#[ignore(reason = "see test_get_dbpath_for_term")]
 fn test_open() {
     open("screen");
     let t = open("nonexistent terminal that hopefully does not exist");