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");