<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Common handling of keyboard input and state management for text input controls

use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::js::JSRef;
use dom::keyboardevent::KeyboardEvent;
use servo_util::str::DOMString;

use std::cmp::{min, max};
use std::default::Default;
use std::num::SignedInt;

#[deriving(Copy, PartialEq)]
enum Selection {
    Selected,
    NotSelected
}

#[jstraceable]
#[deriving(Copy)]
struct TextPoint {
    /// 0-based line number
    line: uint,
    /// 0-based column number
    index: uint,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[deriving(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[deriving(PartialEq)]
enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.MetaKey() && !event.CtrlKey() && !event.AltKey()
}

#[cfg(not(target_os="macos"))]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.CtrlKey() && !event.MetaKey() && !event.AltKey()
}

impl TextInput {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString) -> TextInput {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, Selection::Selected);
        }
        self.replace_selection("".into_string());
    }

    /// Insert a character at the current editing point
    fn insert_char(&mut self, ch: char) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(ch.to_string());
    }

    fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.selection_begin = None;

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].char_len());
            let lines_prefix = self.lines.slice(0, begin.line);
            let lines_suffix = self.lines.slice(end.line + 1, self.lines.len());

            let mut insert_lines = if self.multiline {
                insert.as_slice().split('\n').map(|s| s.into_string()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.into_string();
            new_line.push_str(insert_lines[0].as_slice());
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].char_len();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(insert_lines.as_slice());
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    fn current_line_length(&self) -> uint {
        self.lines[self.edit_point.line].char_len()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    fn adjust_vertical(&mut self, adjust: int, select: Selection) {
        if !self.multiline {
            return;
        }

        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.selection_begin = None;
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: int = self.edit_point.line as int + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as uint >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as uint;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    fn adjust_horizontal(&mut self, adjust: int, select: Selection) {
        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.selection_begin = None;
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as uint > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as int + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as int + adjust) as uint;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as uint > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as int - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as uint);
            }
        }
    }

    /// Deal with a newline input.
    fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].char_len();
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        //A simple way to convert an event to a selection
        fn maybe_select(event: JSRef<KeyboardEvent>) -> Selection {
            if event.ShiftKey() {
                return Selection::Selected
            }
            return Selection::NotSelected
        }
        match event.Key().as_slice() {
           "a" if is_control_key(event) => {
                self.select_all();
                KeyReaction::Nothing
            },
            // printable characters have single-character key values
            c if c.len() == 1 => {
                self.insert_char(c.char_at(0));
                return KeyReaction::DispatchInput;
            }
            "Space" => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            "Delete" => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            "Backspace" => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            "ArrowLeft" => {
                self.adjust_horizontal(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowRight" => {
                self.adjust_horizontal(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowUp" => {
                self.adjust_vertical(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowDown" => {
                self.adjust_vertical(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "Enter" => self.handle_return(),
            "Home" => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            "End" => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            "PageUp" => {
                self.adjust_vertical(-28, maybe_select(event));
                KeyReaction::Nothing
            }
            "PageDown" => {
                self.adjust_vertical(28, maybe_select(event));
                KeyReaction::Nothing
            }
            "Tab" => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".into_string();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(line.as_slice());
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.as_slice().split('\n').map(|s| s.into_string()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);

        if self.current_line_length() == 0 {
            self.edit_point.index = 0;
        }
        else {
            self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1);
        }
    }
}
<commit_msg>added unit tests for TextInput - fixes #4352<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Common handling of keyboard input and state management for text input controls

use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::js::JSRef;
use dom::keyboardevent::KeyboardEvent;
use servo_util::str::DOMString;

use std::cmp::{min, max};
use std::default::Default;
use std::num::SignedInt;

#[deriving(Copy, PartialEq)]
enum Selection {
    Selected,
    NotSelected
}

#[jstraceable]
#[deriving(Copy)]
struct TextPoint {
    /// 0-based line number
    line: uint,
    /// 0-based column number
    index: uint,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[deriving(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[deriving(PartialEq)]
enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.MetaKey() && !event.CtrlKey() && !event.AltKey()
}

#[cfg(not(target_os="macos"))]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.CtrlKey() && !event.MetaKey() && !event.AltKey()
}

impl TextInput {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString) -> TextInput {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, Selection::Selected);
        }
        self.replace_selection("".into_string());
    }

    /// Insert a character at the current editing point
    fn insert_char(&mut self, ch: char) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(ch.to_string());
    }

    fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.clear_selection();

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].char_len());
            let lines_prefix = self.lines.slice(0, begin.line);
            let lines_suffix = self.lines.slice(end.line + 1, self.lines.len());

            let mut insert_lines = if self.multiline {
                insert.as_slice().split('\n').map(|s| s.into_string()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.into_string();
            new_line.push_str(insert_lines[0].as_slice());
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].char_len();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(insert_lines.as_slice());
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    fn current_line_length(&self) -> uint {
        self.lines[self.edit_point.line].char_len()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    fn adjust_vertical(&mut self, adjust: int, select: Selection) {
        if !self.multiline {
            return;
        }

        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.clear_selection();
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: int = self.edit_point.line as int + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as uint >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as uint;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    fn adjust_horizontal(&mut self, adjust: int, select: Selection) {
        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.clear_selection();
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as uint > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as int + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as int + adjust) as uint;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as uint > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as int - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as uint);
            }
        }
    }

    /// Deal with a newline input.
    fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].char_len();
    }

    /// Remove the current selection.
    fn clear_selection(&mut self) {
        self.selection_begin = None;
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        //A simple way to convert an event to a selection
        fn maybe_select(event: JSRef<KeyboardEvent>) -> Selection {
            if event.ShiftKey() {
                return Selection::Selected
            }
            return Selection::NotSelected
        }
        match event.Key().as_slice() {
           "a" if is_control_key(event) => {
                self.select_all();
                KeyReaction::Nothing
            },
            // printable characters have single-character key values
            c if c.len() == 1 => {
                self.insert_char(c.char_at(0));
                return KeyReaction::DispatchInput;
            }
            "Space" => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            "Delete" => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            "Backspace" => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            "ArrowLeft" => {
                self.adjust_horizontal(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowRight" => {
                self.adjust_horizontal(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowUp" => {
                self.adjust_vertical(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowDown" => {
                self.adjust_vertical(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "Enter" => self.handle_return(),
            "Home" => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            "End" => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            "PageUp" => {
                self.adjust_vertical(-28, maybe_select(event));
                KeyReaction::Nothing
            }
            "PageDown" => {
                self.adjust_vertical(28, maybe_select(event));
                KeyReaction::Nothing
            }
            "Tab" => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".into_string();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(line.as_slice());
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.as_slice().split('\n').map(|s| s.into_string()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);

        if self.current_line_length() == 0 {
            self.edit_point.index = 0;
        }
        else {
            self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1);
        }
    }
}

#[test]
fn test_textinput_delete_char() {
    let mut textinput = TextInput::new(Lines::Single, "abcdefg".into_string());
    textinput.adjust_horizontal(2, Selection::NotSelected);
    textinput.delete_char(DeleteDir::Backward);
    assert_eq!(textinput.get_content().as_slice(), "acdefg");

    textinput.delete_char(DeleteDir::Forward);
    assert_eq!(textinput.get_content().as_slice(), "adefg");

    textinput.adjust_horizontal(2, Selection::Selected);
    textinput.delete_char(DeleteDir::Forward);
    assert_eq!(textinput.get_content().as_slice(), "afg");
}

#[test]
fn test_textinput_insert_char() {
    let mut textinput = TextInput::new(Lines::Single, "abcdefg".into_string());
    textinput.adjust_horizontal(2, Selection::NotSelected);
    textinput.insert_char('a');
    assert_eq!(textinput.get_content().as_slice(), "abacdefg");

    textinput.adjust_horizontal(2, Selection::Selected);
    textinput.insert_char('b');
    assert_eq!(textinput.get_content().as_slice(), "ababefg");
}

#[test]
fn test_textinput_get_sorted_selection() {
    let mut textinput = TextInput::new(Lines::Single, "abcdefg".into_string());
    textinput.adjust_horizontal(2, Selection::NotSelected);
    textinput.adjust_horizontal(2, Selection::Selected);
    let (begin, end) = textinput.get_sorted_selection();
    assert_eq!(begin.index, 2);
    assert_eq!(end.index, 4);

    textinput.clear_selection();

    textinput.adjust_horizontal(-2, Selection::Selected);
    let (begin, end) = textinput.get_sorted_selection();
    assert_eq!(begin.index, 2);
    assert_eq!(end.index, 4);
}

#[test]
fn test_textinput_replace_selection() {
    let mut textinput = TextInput::new(Lines::Single, "abcdefg".into_string());
    textinput.adjust_horizontal(2, Selection::NotSelected);
    textinput.adjust_horizontal(2, Selection::Selected);

    textinput.replace_selection("xyz".into_string());
    assert_eq!(textinput.get_content().as_slice(), "abxyzefg");
}

#[test]
fn test_textinput_current_line_length() {
    let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    assert_eq!(textinput.current_line_length(), 3);

    textinput.adjust_vertical(1, Selection::NotSelected);
    assert_eq!(textinput.current_line_length(), 2);

    textinput.adjust_vertical(1, Selection::NotSelected);
    assert_eq!(textinput.current_line_length(), 1);
}

#[test]
fn test_textinput_adjust_vertical() {
    let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    textinput.adjust_horizontal(3, Selection::NotSelected);
    textinput.adjust_vertical(1, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 1);
    assert_eq!(textinput.edit_point.index, 2);

    textinput.adjust_vertical(-1, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 0);
    assert_eq!(textinput.edit_point.index, 2);

    textinput.adjust_vertical(2, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 2);
    assert_eq!(textinput.edit_point.index, 1);
}

#[test]
fn test_textinput_adjust_horizontal() {
    let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    textinput.adjust_horizontal(4, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 1);
    assert_eq!(textinput.edit_point.index, 0);

    textinput.adjust_horizontal(1, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 1);
    assert_eq!(textinput.edit_point.index, 1);

    textinput.adjust_horizontal(2, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 2);
    assert_eq!(textinput.edit_point.index, 0);

    textinput.adjust_horizontal(-1, Selection::NotSelected);
    assert_eq!(textinput.edit_point.line, 1);
    assert_eq!(textinput.edit_point.index, 2);
}

#[test]
fn test_textinput_handle_return() {
    let mut single_line_textinput = TextInput::new(Lines::Single, "abcdef".into_string());
    single_line_textinput.adjust_horizontal(3, Selection::NotSelected);
    single_line_textinput.handle_return();
    assert_eq!(single_line_textinput.get_content().as_slice(), "abcdef");

    let mut multi_line_textinput = TextInput::new(Lines::Multiple, "abcdef".into_string());
    multi_line_textinput.adjust_horizontal(3, Selection::NotSelected);
    multi_line_textinput.handle_return();
    assert_eq!(multi_line_textinput.get_content().as_slice(), "abc\ndef");
}

#[test]
fn test_textinput_select_all() {
    let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    assert_eq!(textinput.edit_point.line, 0);
    assert_eq!(textinput.edit_point.index, 0);

    textinput.select_all();
    assert_eq!(textinput.edit_point.line, 2);
    assert_eq!(textinput.edit_point.index, 1);
}

#[test]
fn test_textinput_get_content() {
    let single_line_textinput = TextInput::new(Lines::Single, "abcdefg".into_string());
    assert_eq!(single_line_textinput.get_content().as_slice(), "abcdefg");

    let multi_line_textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    assert_eq!(multi_line_textinput.get_content().as_slice(), "abc\nde\nf");
}

#[test]
fn test_textinput_set_content() {
    let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".into_string());
    assert_eq!(textinput.get_content().as_slice(), "abc\nde\nf");

    textinput.set_content("abc\nf".into_string());
    assert_eq!(textinput.get_content().as_slice(), "abc\nf");

    assert_eq!(textinput.edit_point.line, 0);
    assert_eq!(textinput.edit_point.index, 0);
    textinput.adjust_horizontal(3, Selection::Selected);
    assert_eq!(textinput.edit_point.line, 0);
    assert_eq!(textinput.edit_point.index, 3);
    textinput.set_content("de".into_string());
    assert_eq!(textinput.get_content().as_slice(), "de");
    assert_eq!(textinput.edit_point.line, 0);
    // FIXME: https://github.com/servo/servo/issues/4622.
    assert_eq!(textinput.edit_point.index, 1);
}

<|endoftext|>


<commit_before>// Copyright 2017 Robert L Snyder, Ithaca, NY <zoltatech@gmail.com>
//                                            <robscary@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate flumph;

use flumph::table::Table;
use flumph::field::Field;
use std::string::String;
use std::path::Path;
use std::fs::remove_file;

#[test]
fn test_table_header() {

    let mut table = Table::new(String::from("test.tbl"), String::from("."));
    let mut field = Field::new(String::from("ID"));
    let mut another_field = Field::new(String::from("Name"));
    let path = Path::new("test.tbl");

    if path.exists() {
        match remove_file(path) {
            Ok(_) => (),
            Err(e) => panic!("Found an existing test file and could not remove it: {}", e.to_string())
        }
    }

    field.length = 40;
    field.offset = 0;
    table.fields.push(field);

    another_field.length=60;
    another_field.offset=40;
    table.fields.push(another_field);

    // You would never do this, but we're testing the header creation so we need some
    // non-zero values. Note that record length should get replaced with the actual
    // length (100)

    table.record_length = 0xF00FBABE;
    table.record_count = 0xFF00FF00DDEEDDEE;

    // First time through should create the header
    let _ = match table.open() {
        Ok(table_file) => table_file,
        Err(e) => panic!("Failed creating table header: {}", e.to_string())
    };

    if !path.exists() {
        panic!("File was not created, but no error was raised...");
    }

    let mut table = Table::new(String::from("test.tbl"), String::from("."));
    let _ = table.open();

    assert_eq!(table.record_length, 100);
    assert_eq!(table.record_count, 0xFF00FF00DDEEDDEE);
    assert_eq!(table.fields.len(), 2);

    let field = &table.fields[0];
    assert_eq!(field.length, 40);
    assert_eq!(field.offset, 0);
    assert_eq!(field.field_name, "ID");

    let field = &table.fields[1];
    assert_eq!(field.length, 60);
    assert_eq!(field.offset, 40);
    assert_eq!(field.field_name, "Name");

}<commit_msg>Documenting and cleaning up Table Header test<commit_after>// Copyright 2017 Robert L Snyder, Ithaca, NY <zoltatech@gmail.com>
//                                            <robscary@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate flumph;

use flumph::table::Table;
use flumph::field::Field;
use std::string::String;
use std::path::Path;
use std::fs::remove_file;

// This purpose of the following test is to validate the creation and
// subsequent reading of the table header information.
//
// 1. A new table is created (and if it was there from before, deleted)
// 2. Two fields are added.
// 3. Garbage is added to table metadata
// 4. The table is "created" on disk, causing the header to be written
// 5. The table is re-opened, and causing the header to be read
// 6. The header data is validated for accuracy, including the two fields.

#[test]
fn test_table_header() {

    let mut table = Table::new(String::from("test.tbl"), String::from("."));
    let mut field = Field::new(String::from("ID"));
    let mut another_field = Field::new(String::from("Name"));
    let path = Path::new("test.tbl");

    if path.exists() {
        match remove_file(path) {
            Ok(_) => (),
            Err(e) =>
                panic!("Existing test file could not beremoved: {}",
                e.to_string())
        }
    }

    field.length = 40;
    field.offset = 0;
    table.fields.push(field);

    another_field.length=60;
    another_field.offset=40;
    table.fields.push(another_field);

    // You would never do this, but we're testing the header creation so
    // we need some non-zero values. Note that record length should get
    // replaced with the actual length (100)

    table.record_length = 0xF00FBABE;
    table.record_count = 0xFF00FF00DDEEDDEE;

    // First time through should create the header
    let _ = match table.open() {
        Ok(table_file) => table_file,
        Err(e) => panic!("Failed creating table header: {}", e.to_string())
    };

    if !path.exists() {
        panic!("File was not created, but no error was raised...");
    }

    let mut table = Table::new(String::from("test.tbl"), String::from("."));
    let _ = table.open();

    assert_eq!(table.record_length, 100);
    assert_eq!(table.record_count, 0xFF00FF00DDEEDDEE);
    assert_eq!(table.fields.len(), 2);

    let field = &table.fields[0];
    assert_eq!(field.length, 40);
    assert_eq!(field.offset, 0);
    assert_eq!(field.field_name, "ID");

    let field = &table.fields[1];
    assert_eq!(field.length, 60);
    assert_eq!(field.offset, 40);
    assert_eq!(field.field_name, "Name");

}<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Starts a service from an installed Habitat package.
//!
//! Services run by the Supervisor support one or more *topologies*, which are state machines that
//! handle the lifecycle of a service; they are members of a *group*, which is a namespace for
//! their configuration and state; and they can *watch* another service group, incorporating that
//! groups configuration and state into their own.
//!
//! # Examples
//!
//! ```bash
//! $ hab-sup start acme/redis
//! ```
//!
//! Will start the `redis` service in the `default` group, using the `standalone` topology.
//!
//! ```bash
//! $ hab-sup start acme/redis -g production
//! ```
//!
//! Will do the same, but in the `production` group.
//!
//! ```bash
//! $ hab-sup start haproxy -w redis.production
//! ```
//!
//! Will start the `haproxy` service, and have it watch the configuration for the `redis`
//! `production` group (note the `.` as the separator.)
//!
//! ```bash
//! $ hab-sup start acme/redis -t leader
//! ```
//!
//! Will start the `redis` service using the `leader` topology.
//!
//! ```bash
//! $ hab-sup start acme/redis -t leader -g production -w haproxy.default
//! ```
//!
//! Will start the `redis` service using the `leader` topology in the `production` group, while
//! watching the `haproxy` `default` group's configuration.
//!
//! See the [documentation on topologies](../topology) for a deeper discussion of how they function.
//!

use std::env;
use std::path::Path;

use ansi_term::Colour::Yellow;
use common::command::package::install;
use common::ui::UI;
use depot_client::Client;
use hcore::fs::{am_i_root, cache_artifact_path, FS_ROOT_PATH};
use hcore::package::PackageIdent;

use {PRODUCT, VERSION};
use error::{Error, Result};
use config::gconfig;
use package::Package;
use manager::Manager;
use manager::{Service, UpdateStrategy};

static LOGKEY: &'static str = "CS";

/// Creates a [Package](../../pkg/struct.Package.html), then passes it to the run method of the
/// selected [topology](../../topology).
///
/// # Failures
///
/// * Fails if it cannot find a package with the given name
/// * Fails if the `run` method for the topology fails
/// * Fails if an unknown topology was specified on the command line
pub fn package() -> Result<()> {
    let mut ui = UI::default();
    if !am_i_root() {
        try!(ui.warn("Running the Habitat Supervisor requires root or administrator privileges. \
                      Please retry this command as a super user or use a privilege-granting \
                      facility such as sudo."));
        try!(ui.br());
        return Err(sup_error!(Error::RootRequired));
    }

    match Package::load(gconfig().package(), None) {
        Ok(mut package) => {
            let update_strategy = gconfig().update_strategy();
            match update_strategy {
                UpdateStrategy::None => {}
                _ => {
                    let url = gconfig().url();
                    outputln!("Checking Depot for newer versions...");
                    // It is important to pass `gconfig().package()` to `show_package()` instead
                    // of the package identifier of the loaded package. This will ensure that
                    // if the operator starts a package while specifying a version number, they
                    // will only automatically receive release updates for the started package.
                    //
                    // If the operator does not specify a version number they will
                    // automatically receive updates for any releases, regardless of version
                    // number, for the started  package.
                    let depot_client = try!(Client::new(url, PRODUCT, VERSION, None));
                    let latest_pkg_data = try!(depot_client.show_package(gconfig().package()));
                    let latest_ident: PackageIdent = latest_pkg_data.get_ident().clone().into();
                    if &latest_ident > package.ident() {
                        outputln!("Downloading latest version from Depot: {}", latest_ident);
                        let new_pkg_data = try!(install::start(&mut ui,
                                                               url,
                                                               &latest_ident.to_string(),
                                                               PRODUCT,
                                                               VERSION,
                                                               Path::new(FS_ROOT_PATH),
                                                               &cache_artifact_path(None),
                                                               false));
                        package = try!(Package::load(&new_pkg_data, None));
                    } else {
                        outputln!("Already running latest.");
                    };
                }
            }
            start_package(package)
        }
        Err(_) => {
            outputln!("{} is not installed",
                      Yellow.bold().paint(gconfig().package().to_string()));
            let url = gconfig().url();
            let new_pkg_data = match gconfig().local_artifact() {
                Some(artifact) => {
                    try!(install::start(&mut ui,
                                        url,
                                        &artifact,
                                        PRODUCT,
                                        VERSION,
                                        Path::new(FS_ROOT_PATH),
                                        &cache_artifact_path(None),
                                        false))
                }
                None => {
                    outputln!("Searching for {} in remote {}",
                              Yellow.bold().paint(gconfig().package().to_string()),
                              url);
                    try!(install::start(&mut ui,
                                        url,
                                        &gconfig().package().to_string(),
                                        PRODUCT,
                                        VERSION,
                                        Path::new(FS_ROOT_PATH),
                                        &cache_artifact_path(None),
                                        false))
                }
            };
            let package = try!(Package::load(&new_pkg_data, None));
            start_package(package)
        }
    }
}

fn start_package(package: Package) -> Result<()> {
    let run_path = try!(package.run_path());
    debug!("Setting the PATH to {}", run_path);
    env::set_var("PATH", &run_path);

    let mut manager = try!(Manager::new());
    let service = try!(Service::new(package,
                                    gconfig().group(),
                                    gconfig().organization().clone(),
                                    gconfig().topology(),
                                    gconfig().update_strategy()));
    try!(manager.add_service(service));
    manager.run()
}
<commit_msg>[sup] Remove comments about -w (watch) which no longer exists<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Starts a service from an installed Habitat package.
//!
//! Services run by the Supervisor support one or more *topologies*, which are state machines that
//! handle the lifecycle of a service; they are members of a *group*, which is a namespace for
//! their configuration and state.
//!
//! # Examples
//!
//! ```bash
//! $ hab-sup start acme/redis
//! ```
//!
//! Will start the `redis` service in the `default` group, using the `standalone` topology.
//!
//! ```bash
//! $ hab-sup start acme/redis -g production
//! ```
//!
//! Will do the same, but in the `production` group.
//!
//! ```bash
//! $ hab-sup start acme/redis -t leader
//! ```
//!
//! Will start the `redis` service using the `leader` topology.
//!
//! ```bash
//! $ hab-sup start acme/redis -t leader -g production
//! ```
//!
//! Will start the `redis` service using the `leader` topology in the `production` group.
//!
//! See the [documentation on topologies](../topology) for a deeper discussion of how they function.
//!

use std::env;
use std::path::Path;

use ansi_term::Colour::Yellow;
use common::command::package::install;
use common::ui::UI;
use depot_client::Client;
use hcore::fs::{am_i_root, cache_artifact_path, FS_ROOT_PATH};
use hcore::package::PackageIdent;

use {PRODUCT, VERSION};
use error::{Error, Result};
use config::gconfig;
use package::Package;
use manager::Manager;
use manager::{Service, UpdateStrategy};

static LOGKEY: &'static str = "CS";

/// Creates a [Package](../../pkg/struct.Package.html), then passes it to the run method of the
/// selected [topology](../../topology).
///
/// # Failures
///
/// * Fails if it cannot find a package with the given name
/// * Fails if the `run` method for the topology fails
/// * Fails if an unknown topology was specified on the command line
pub fn package() -> Result<()> {
    let mut ui = UI::default();
    if !am_i_root() {
        try!(ui.warn("Running the Habitat Supervisor requires root or administrator privileges. \
                      Please retry this command as a super user or use a privilege-granting \
                      facility such as sudo."));
        try!(ui.br());
        return Err(sup_error!(Error::RootRequired));
    }

    match Package::load(gconfig().package(), None) {
        Ok(mut package) => {
            let update_strategy = gconfig().update_strategy();
            match update_strategy {
                UpdateStrategy::None => {}
                _ => {
                    let url = gconfig().url();
                    outputln!("Checking Depot for newer versions...");
                    // It is important to pass `gconfig().package()` to `show_package()` instead
                    // of the package identifier of the loaded package. This will ensure that
                    // if the operator starts a package while specifying a version number, they
                    // will only automatically receive release updates for the started package.
                    //
                    // If the operator does not specify a version number they will
                    // automatically receive updates for any releases, regardless of version
                    // number, for the started  package.
                    let depot_client = try!(Client::new(url, PRODUCT, VERSION, None));
                    let latest_pkg_data = try!(depot_client.show_package(gconfig().package()));
                    let latest_ident: PackageIdent = latest_pkg_data.get_ident().clone().into();
                    if &latest_ident > package.ident() {
                        outputln!("Downloading latest version from Depot: {}", latest_ident);
                        let new_pkg_data = try!(install::start(&mut ui,
                                                               url,
                                                               &latest_ident.to_string(),
                                                               PRODUCT,
                                                               VERSION,
                                                               Path::new(FS_ROOT_PATH),
                                                               &cache_artifact_path(None),
                                                               false));
                        package = try!(Package::load(&new_pkg_data, None));
                    } else {
                        outputln!("Already running latest.");
                    };
                }
            }
            start_package(package)
        }
        Err(_) => {
            outputln!("{} is not installed",
                      Yellow.bold().paint(gconfig().package().to_string()));
            let url = gconfig().url();
            let new_pkg_data = match gconfig().local_artifact() {
                Some(artifact) => {
                    try!(install::start(&mut ui,
                                        url,
                                        &artifact,
                                        PRODUCT,
                                        VERSION,
                                        Path::new(FS_ROOT_PATH),
                                        &cache_artifact_path(None),
                                        false))
                }
                None => {
                    outputln!("Searching for {} in remote {}",
                              Yellow.bold().paint(gconfig().package().to_string()),
                              url);
                    try!(install::start(&mut ui,
                                        url,
                                        &gconfig().package().to_string(),
                                        PRODUCT,
                                        VERSION,
                                        Path::new(FS_ROOT_PATH),
                                        &cache_artifact_path(None),
                                        false))
                }
            };
            let package = try!(Package::load(&new_pkg_data, None));
            start_package(package)
        }
    }
}

fn start_package(package: Package) -> Result<()> {
    let run_path = try!(package.run_path());
    debug!("Setting the PATH to {}", run_path);
    env::set_var("PATH", &run_path);

    let mut manager = try!(Manager::new());
    let service = try!(Service::new(package,
                                    gconfig().group(),
                                    gconfig().organization().clone(),
                                    gconfig().topology(),
                                    gconfig().update_strategy()));
    try!(manager.add_service(service));
    manager.run()
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::ffi::OsStr;
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};

use sys::abilities;

use hcore::os;

use error::{Error, Result};
use manager::service::Pkg;

static LOGKEY: &'static str = "EX";

pub fn run<T, S>(path: S, pkg: &Pkg, _: Option<T>) -> Result<Child>
where
    T: ToString,
    S: AsRef<OsStr>,
{
    let mut cmd = Command::new(path);
    cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(
        Stdio::piped(),
    );
    for (key, val) in pkg.env.iter() {
        cmd.env(key, val);
    }

    if abilities::can_run_services_as_svc_user() {
        // If we can SETUID/SETGID, then run the script as the service
        // user; otherwise, we'll just run it as ourselves.

        let uid = os::users::get_uid_by_name(&pkg.svc_user).ok_or(sup_error!(
            Error::Permissions(format!(
                "No uid for user '{}' could be found",
                &pkg.svc_user
            ))
        ))?;
        let gid = os::users::get_gid_by_name(&pkg.svc_group).ok_or(
            sup_error!(
                Error::Permissions(format!(
                    "No gid for group '{}' could be found",
                    &pkg.svc_group
                ))
            ),
        )?;

        cmd.uid(uid).gid(gid);
    } else {
        debug!("Cannot SETUID/SETGID; running as supervisor user instead of SVC_USER");
    }

    Ok(cmd.spawn()?)
}
<commit_msg>Harmonize non-root log message for hooks with one for service<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::ffi::OsStr;
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};

use sys::abilities;

use hcore::os;

use error::{Error, Result};
use manager::service::Pkg;

static LOGKEY: &'static str = "EX";

pub fn run<T, S>(path: S, pkg: &Pkg, _: Option<T>) -> Result<Child>
where
    T: ToString,
    S: AsRef<OsStr>,
{
    let mut cmd = Command::new(path.as_ref());
    cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(
        Stdio::piped(),
    );
    for (key, val) in pkg.env.iter() {
        cmd.env(key, val);
    }

    if abilities::can_run_services_as_svc_user() {
        // If we can SETUID/SETGID, then run the script as the service
        // user; otherwise, we'll just run it as ourselves.

        let uid = os::users::get_uid_by_name(&pkg.svc_user).ok_or(sup_error!(
            Error::Permissions(format!(
                "No uid for user '{}' could be found",
                &pkg.svc_user
            ))
        ))?;
        let gid = os::users::get_gid_by_name(&pkg.svc_group).ok_or(
            sup_error!(
                Error::Permissions(format!(
                    "No gid for group '{}' could be found",
                    &pkg.svc_group
                ))
            ),
        )?;

        cmd.uid(uid).gid(gid);
    } else {
        debug!(
            "Current user lacks sufficient capabilites to run {:?} as \"{}\"; running as self!",
            path.as_ref(),
            &pkg.svc_user
        );
    }

    Ok(cmd.spawn()?)
}
<|endoftext|>


<commit_before>use std::io;
use std::io::prelude::*;
use ansi_term::{Colour, Style};
use libc;
use slsr_core::puzzle::{Puzzle, Edge, Side};
use slsr_core::geom::{Geom, Point, Move};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Mode {
    Color,
    Ascii,
}

#[derive(Copy, Clone, Debug)]
pub struct Config {
    pub mode: Mode,
    pub cell_width: usize,
    pub cell_height: usize,
}

fn side_to_style(ty: Option<Side>) -> Style {
    match ty {
        Some(Side::In) => Colour::Black.on(Colour::Yellow),
        Some(Side::Out) => Colour::White.on(Colour::Black),
        None => Colour::Black.on(Colour::White),
    }
}

#[cfg(unix)]
fn isatty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) != 0 }
}
#[cfg(windows)]
fn isatty(fd: libc::c_int) -> bool {
    extern crate kernel32;
    extern crate winapi;
    unsafe {
        let handle = kernel32::GetStdHandle(if fd == libc::STDOUT_FILENO {
            winapi::winbase::STD_OUTPUT_HANDLE
        } else {
            winapi::winbase::STD_ERROR_HANDLE
        });
        let mut out = 0;
        kernel32::GetConsoleMode(handle, &mut out) != 0
    }
}

pub fn is_pprintable() -> bool {
    isatty(libc::STDOUT_FILENO)
}

struct Printer<T> {
    is_color: bool,
    out: T,
}

impl<T> Printer<T>
    where T: Write
{
    fn new(is_color: bool, out: T) -> Printer<T> {
        Printer {
            is_color: is_color,
            out: out,
        }
    }

    fn write_pretty(&mut self, side: Option<Side>, s: &str) -> io::Result<()> {
        if self.is_color {
            self.out.write_fmt(format_args!("{}", side_to_style(side).paint(s)))
        } else {
            self.out.write_all(s.as_bytes())
        }
    }

    fn write_plain(&mut self, s: &str) -> io::Result<()> {
        self.out.write_all(s.as_bytes())
    }
}

struct Table;
impl Table {
    fn pprint<T>(printer: &mut Printer<T>, conf: &Config, puzzle: &Puzzle) -> io::Result<()>
        where T: Write
    {
        let row = puzzle.row();
        try!(LabelRow::pprint(printer, conf, puzzle));
        for y in 0..row {
            try!(EdgeRow::pprint(printer, conf, puzzle, y));
            try!(CellRow::pprint(printer, conf, puzzle, y));
        }
        try!(EdgeRow::pprint(printer, conf, puzzle, row));
        try!(LabelRow::pprint(printer, conf, puzzle));
        Ok(())
    }
}

struct LabelRow;
impl LabelRow {
    fn pprint<T>(printer: &mut Printer<T>, conf: &Config, puzzle: &Puzzle) -> io::Result<()>
        where T: Write
    {
        try!(printer.write_plain(&format!("{:1$}", "", conf.cell_width)));
        for x in 0..puzzle.column() {
            try!(printer.write_plain(&format!("{:1$}", "", 1)));
            try!(Label::pprint(printer, conf, x, true));
        }
        try!(printer.write_plain("\n"));
        Ok(())
    }
}

struct EdgeRow;
impl EdgeRow {
    fn pprint<T>(printer: &mut Printer<T>, conf: &Config, puzzle: &Puzzle, y: i32) -> io::Result<()>
        where T: Write
    {
        let col = puzzle.column();
        try!(printer.write_plain(&format!("{:1$}", "", conf.cell_width)));
        for x in 0..col {
            try!(Corner::pprint(printer, conf, puzzle, Point(y, x)));
            try!(EdgeH::pprint(printer, conf, puzzle, Point(y, x)));
        }
        try!(Corner::pprint(printer, conf, puzzle, Point(y, col)));
        try!(printer.write_plain("\n"));
        Ok(())
    }
}

struct CellRow;
impl CellRow {
    fn pprint<T>(printer: &mut Printer<T>, conf: &Config, puzzle: &Puzzle, y: i32) -> io::Result<()>
        where T: Write
    {
        let col = puzzle.column();
        for i in 0..conf.cell_height {
            let num_line = (conf.cell_height - 1) / 2 == i;
            try!(Label::pprint(printer, conf, y, num_line));
            for x in 0..col {
                try!(EdgeV::pprint(printer, conf, puzzle, Point(y, x)));
                try!(Cell::pprint(printer, conf, puzzle, Point(y, x), num_line));
            }
            try!(EdgeV::pprint(printer, conf, puzzle, Point(y, col)));
            try!(Label::pprint(printer, conf, y, num_line));
            try!(printer.write_plain("\n"));
        }
        Ok(())
    }
}

struct Corner;
impl Corner {
    fn pprint<T>(printer: &mut Printer<T>,
                 _conf: &Config,
                 puzzle: &Puzzle,
                 p: Point)
                 -> io::Result<()>
        where T: Write
    {
        let l = p + Move::LEFT;
        let u = p + Move::UP;
        let eh_p = puzzle.edge_h(p);
        let eh_l = puzzle.edge_h(l);
        let ev_p = puzzle.edge_v(p);
        let ev_u = puzzle.edge_v(u);

        let is_same_all = (eh_p == Some(Edge::Cross)) && (eh_l == Some(Edge::Cross)) &&
                          (ev_p == Some((Edge::Cross))) &&
                          (ev_u == Some((Edge::Cross)));

        let ty = if is_same_all {
            puzzle.side(p)
        } else {
            None
        };
        let is_h = eh_p == Some(Edge::Line) && eh_l == Some(Edge::Line);
        let is_v = ev_p == Some(Edge::Line) && ev_u == Some(Edge::Line);

        if is_same_all {
            try!(printer.write_pretty(ty, "."));
        } else if is_h {
            try!(printer.write_pretty(ty, "-"));
        } else if is_v {
            try!(printer.write_pretty(ty, "|"));
        } else {
            try!(printer.write_pretty(ty, "+"));
        }
        Ok(())
    }
}

struct EdgeH;
impl EdgeH {
    fn pprint<T>(printer: &mut Printer<T>,
                 conf: &Config,
                 puzzle: &Puzzle,
                 p: Point)
                 -> io::Result<()>
        where T: Write
    {
        let (s, ty) = match puzzle.edge_h(p) {
            Some(Edge::Cross) => (" ", puzzle.side(p)),
            Some(Edge::Line) => ("-", None),
            None => ("~", None),
        };
        for _ in 0..conf.cell_width {
            try!(printer.write_pretty(ty, s));
        }
        Ok(())
    }
}

struct Label;
impl Label {
    fn pprint<T>(printer: &mut Printer<T>, conf: &Config, n: i32, num_line: bool) -> io::Result<()>
        where T: Write
    {
        if num_line {
            let order = 10i32.pow(conf.cell_width as u32);
            try!(printer.write_plain(&format!("{:^1$}", n % order, conf.cell_width)));
        } else {
            try!(printer.write_plain(&format!("{:1$}", "", conf.cell_width)));
        }
        Ok(())
    }
}

struct EdgeV;
impl EdgeV {
    fn pprint<T>(printer: &mut Printer<T>,
                 _conf: &Config,
                 puzzle: &Puzzle,
                 p: Point)
                 -> io::Result<()>
        where T: Write
    {
        let (s, ty) = match puzzle.edge_v(p) {
            Some(Edge::Cross) => (" ", puzzle.side(p)),
            Some(Edge::Line) => ("|", None),
            None => ("?", None),
        };
        try!(printer.write_pretty(ty, s));
        Ok(())
    }
}

struct Cell;
impl Cell {
    fn pprint<T>(printer: &mut Printer<T>,
                 conf: &Config,
                 puzzle: &Puzzle,
                 p: Point,
                 num_line: bool)
                 -> io::Result<()>
        where T: Write
    {
        let ty = puzzle.side(p);
        match puzzle.hint(p) {
            Some(x) if num_line => {
                try!(printer.write_pretty(ty, &format!("{:^1$}", x, conf.cell_width)));
            }
            _ => {
                try!(printer.write_pretty(ty, &format!("{:^1$}", "", conf.cell_width)));
            }
        }
        Ok(())
    }
}

pub fn print(conf: &Config, puzzle: &Puzzle) -> io::Result<()> {
    let is_color = conf.mode == Mode::Color;
    Table::pprint(&mut Printer::new(is_color, io::stdout()), conf, puzzle)
}
<commit_msg>cli: pprint performance improvement<commit_after>use std::{io, iter};
use std::io::prelude::*;
use ansi_term::{Colour, Style};
use libc;
use slsr_core::puzzle::{Puzzle, Edge, Side};
use slsr_core::geom::{Geom, Point, Move};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Mode {
    Color,
    Ascii,
}

#[derive(Copy, Clone, Debug)]
pub struct Config {
    pub mode: Mode,
    pub cell_width: usize,
    pub cell_height: usize,
}

fn side_to_style(ty: Option<Side>) -> Style {
    match ty {
        Some(Side::In) => Colour::Black.on(Colour::Yellow),
        Some(Side::Out) => Colour::White.on(Colour::Black),
        None => Colour::Black.on(Colour::White),
    }
}

#[cfg(unix)]
fn isatty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) != 0 }
}
#[cfg(windows)]
fn isatty(fd: libc::c_int) -> bool {
    extern crate kernel32;
    extern crate winapi;
    unsafe {
        let handle = kernel32::GetStdHandle(if fd == libc::STDOUT_FILENO {
            winapi::winbase::STD_OUTPUT_HANDLE
        } else {
            winapi::winbase::STD_ERROR_HANDLE
        });
        let mut out = 0;
        kernel32::GetConsoleMode(handle, &mut out) != 0
    }
}

pub fn is_pprintable() -> bool {
    isatty(libc::STDOUT_FILENO)
}

struct Printer<T> {
    is_color: bool,
    out: T,
}

impl<T> Printer<T>
    where T: Write
{
    fn new(is_color: bool, out: T) -> Printer<T> {
        Printer {
            is_color: is_color,
            out: out,
        }
    }

    fn write_pretty(&mut self, side: Option<Side>, s: &str) -> io::Result<()> {
        if self.is_color {
            self.out.write_fmt(format_args!("{}", side_to_style(side).paint(s)))
        } else {
            self.out.write_all(s.as_bytes())
        }
    }

    fn write_plain(&mut self, s: &str) -> io::Result<()> {
        self.out.write_all(s.as_bytes())
    }
}

struct Table {
    label_row: LabelRow,
    edge_row: EdgeRow,
    cell_row: CellRow,
}

impl Table {
    fn new(conf: &Config) -> Table {
        Table {
            label_row: LabelRow::new(conf),
            edge_row: EdgeRow::new(conf),
            cell_row: CellRow::new(conf),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle) -> io::Result<()>
        where T: Write
    {
        let row = puzzle.row();
        try!(self.label_row.pprint(printer, puzzle));
        for y in 0..row {
            try!(self.edge_row.pprint(printer, puzzle, y));
            try!(self.cell_row.pprint(printer, puzzle, y));
        }
        try!(self.edge_row.pprint(printer, puzzle, row));
        try!(self.label_row.pprint(printer, puzzle));
        Ok(())
    }
}

struct LabelRow {
    space_left: String,
    space_cross: String,
    label: Label,
}

impl LabelRow {
    fn new(conf: &Config) -> LabelRow {
        LabelRow {
            space_left: format!("{:1$}", "", conf.cell_width),
            space_cross: format!("{:1$}", "", 1),
            label: Label::new(conf),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle) -> io::Result<()>
        where T: Write
    {
        try!(printer.write_plain(&self.space_left));
        for x in 0..puzzle.column() {
            try!(printer.write_plain(&self.space_cross));
            try!(self.label.pprint(printer, x, true));
        }
        try!(printer.write_plain("\n"));
        Ok(())
    }
}

struct EdgeRow {
    space_left: String,
    corner: Corner,
    edge_h: EdgeH,
}

impl EdgeRow {
    fn new(conf: &Config) -> EdgeRow {
        EdgeRow {
            space_left: format!("{:1$}", "", conf.cell_width),
            corner: Corner::new(conf),
            edge_h: EdgeH::new(conf),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle, y: i32) -> io::Result<()>
        where T: Write
    {
        let col = puzzle.column();
        try!(printer.write_plain(&self.space_left));
        for x in 0..col {
            try!(self.corner.pprint(printer, puzzle, Point(y, x)));
            try!(self.edge_h.pprint(printer, puzzle, Point(y, x)));
        }
        try!(self.corner.pprint(printer, puzzle, Point(y, col)));
        try!(printer.write_plain("\n"));
        Ok(())
    }
}

struct CellRow {
    cell_height: usize,
    edge_v: EdgeV,
    label: Label,
    cell: Cell,
}

impl CellRow {
    fn new(conf: &Config) -> CellRow {
        CellRow {
            cell_height: conf.cell_height,
            edge_v: EdgeV::new(conf),
            label: Label::new(conf),
            cell: Cell::new(conf),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle, y: i32) -> io::Result<()>
        where T: Write
    {
        let col = puzzle.column();
        for i in 0..self.cell_height {
            let num_line = (self.cell_height - 1) / 2 == i;
            try!(self.label.pprint(printer, y, num_line));
            for x in 0..col {
                try!(self.edge_v.pprint(printer, puzzle, Point(y, x)));
                try!(self.cell.pprint(printer, puzzle, Point(y, x), num_line));
            }
            try!(self.edge_v.pprint(printer, puzzle, Point(y, col)));
            try!(self.label.pprint(printer, y, num_line));
            try!(printer.write_plain("\n"));
        }
        Ok(())
    }
}

struct Corner;
impl Corner {
    fn new(_conf: &Config) -> Corner {
        Corner
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle, p: Point) -> io::Result<()>
        where T: Write
    {
        let l = p + Move::LEFT;
        let u = p + Move::UP;
        let eh_p = puzzle.edge_h(p);
        let eh_l = puzzle.edge_h(l);
        let ev_p = puzzle.edge_v(p);
        let ev_u = puzzle.edge_v(u);

        let is_same_all = (eh_p == Some(Edge::Cross)) && (eh_l == Some(Edge::Cross)) &&
                          (ev_p == Some((Edge::Cross))) &&
                          (ev_u == Some((Edge::Cross)));

        let side = if is_same_all {
            puzzle.side(p)
        } else {
            None
        };
        let is_h = eh_p == Some(Edge::Line) && eh_l == Some(Edge::Line);
        let is_v = ev_p == Some(Edge::Line) && ev_u == Some(Edge::Line);

        if is_same_all {
            try!(printer.write_pretty(side, "."));
        } else if is_h {
            try!(printer.write_pretty(side, "-"));
        } else if is_v {
            try!(printer.write_pretty(side, "|"));
        } else {
            try!(printer.write_pretty(side, "+"));
        }
        Ok(())
    }
}

struct EdgeH {
    str_cross: String,
    str_line: String,
    str_unknown: String,
}

impl EdgeH {
    fn new(conf: &Config) -> EdgeH {
        EdgeH {
            str_cross: iter::repeat(' ').take(conf.cell_width).collect(),
            str_line: iter::repeat('-').take(conf.cell_width).collect(),
            str_unknown: iter::repeat('~').take(conf.cell_width).collect(),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle, p: Point) -> io::Result<()>
        where T: Write
    {
        let (s, side) = match puzzle.edge_h(p) {
            Some(Edge::Cross) => (&self.str_cross, puzzle.side(p)),
            Some(Edge::Line) => (&self.str_line, None),
            None => (&self.str_unknown, None),
        };
        try!(printer.write_pretty(side, s));
        Ok(())
    }
}

struct Label {
    width: usize,
    order: i32,
    space: String,
}

impl Label {
    fn new(conf: &Config) -> Label {
        Label {
            width: conf.cell_width,
            order: 10i32.pow(conf.cell_width as u32),
            space: format!("{:1$}", "", conf.cell_width),
        }
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, n: i32, num_line: bool) -> io::Result<()>
        where T: Write
    {
        if num_line {
            try!(printer.write_plain(&format!("{:^1$}", n % self.order, self.width)));
        } else {
            try!(printer.write_plain(&self.space));
        }
        Ok(())
    }
}

struct EdgeV;

impl EdgeV {
    fn new(_conf: &Config) -> EdgeV {
        EdgeV
    }

    fn pprint<T>(&self, printer: &mut Printer<T>, puzzle: &Puzzle, p: Point) -> io::Result<()>
        where T: Write
    {
        let (s, side) = match puzzle.edge_v(p) {
            Some(Edge::Cross) => (" ", puzzle.side(p)),
            Some(Edge::Line) => ("|", None),
            None => ("?", None),
        };
        try!(printer.write_pretty(side, s));
        Ok(())
    }
}

struct Cell {
    nums: [String; 5],
    space: String,
}

impl Cell {
    fn new(conf: &Config) -> Cell {
        Cell {
            nums: [format!("{:^1$}", 0, conf.cell_width),
                   format!("{:^1$}", 1, conf.cell_width),
                   format!("{:^1$}", 2, conf.cell_width),
                   format!("{:^1$}", 3, conf.cell_width),
                   format!("{:^1$}", 4, conf.cell_width)],
            space: format!("{:^1$}", "", conf.cell_width),
        }
    }

    fn pprint<T>(&self,
                 printer: &mut Printer<T>,
                 puzzle: &Puzzle,
                 p: Point,
                 num_line: bool)
                 -> io::Result<()>
        where T: Write
    {
        let side = puzzle.side(p);
        match puzzle.hint(p) {
            Some(x) if num_line => {
                try!(printer.write_pretty(side, &self.nums[x as usize]))
            }
            _ => {
                try!(printer.write_pretty(side, &self.space))
            }
        }
        Ok(())
    }
}

pub fn print(conf: &Config, puzzle: &Puzzle) -> io::Result<()> {
    let is_color = conf.mode == Mode::Color;

    let table = Table::new(conf);
    let mut printer = Printer::new(is_color, io::stdout());

    table.pprint(&mut printer, puzzle)
}
<|endoftext|>


<commit_before>use std::{env,
          io::Error,
          ptr::{null,
                null_mut}};

use widestring::WideCString;
use winapi::{shared::{minwindef::{BOOL,
                                  LPDWORD},
                      ntdef::LPCWSTR,
                      winerror::*},
             um::{winbase::LookupAccountSidW,
                  winnt::{PSID,
                          PSID_NAME_USE,
                          SID_NAME_USE}}};

use super::sid::Sid;

extern "system" {
    fn LookupAccountNameW(lpSystemName: LPCWSTR,
                          lpAccountName: LPCWSTR,
                          Sid: PSID,
                          cbSid: LPDWORD,
                          ReferencedDomainName: LPCWSTR,
                          cchReferencedDomainName: LPDWORD,
                          peUse: PSID_NAME_USE)
                          -> BOOL;
}

pub struct Account {
    pub name:         String,
    pub system_name:  Option<String>,
    pub domain:       String,
    pub account_type: SID_NAME_USE,
    pub sid:          Sid,
}

impl Account {
    pub fn from_name(name: &str) -> Option<Account> { lookup_account(name, None) }

    pub fn from_name_and_system(name: &str, system_name: &str) -> Option<Account> {
        lookup_account(name, Some(system_name.to_string()))
    }

    pub fn from_sid(sid: &str) -> Option<Account> {
        let sid = if let Ok(sid) = Sid::from_str(sid) {
            sid
        } else {
            return None;
        };
        let mut name_size: u32 = 0;
        let mut domain_size: u32 = 0;
        unsafe {
            LookupAccountSidW(null(),
                              sid.raw.as_ptr() as *mut _,
                              null_mut(),
                              &mut name_size as LPDWORD,
                              null_mut(),
                              &mut domain_size as LPDWORD,
                              null_mut())
        };
        match Error::last_os_error().raw_os_error().unwrap() as u32 {
            ERROR_INSUFFICIENT_BUFFER => {}
            ERROR_NONE_MAPPED => return None,
            _ => {
                debug!("Error while looking up account for {}: {}",
                       sid.to_string().expect("to convert sid to string"),
                       Error::last_os_error());
                return None;
            }
        }

        let mut name: Vec<u16> = Vec::with_capacity(name_size as usize);
        let mut domain: Vec<u16> = Vec::with_capacity(domain_size as usize);
        let mut sid_type = 0;

        let ret = unsafe {
            LookupAccountSidW(null(),
                              sid.raw.as_ptr() as *mut _,
                              name.as_mut_ptr(),
                              &mut name_size as LPDWORD,
                              domain.as_mut_ptr(),
                              &mut domain_size as LPDWORD,
                              &mut sid_type as PSID_NAME_USE)
        };
        if ret == 0 {
            error!("Failed to retrieve name for {}: {}",
                   sid.to_string().expect("to convert sid to string"),
                   Error::last_os_error());
            return None;
        }
        unsafe {
            name.set_len(name_size as usize);
            domain.set_len(domain_size as usize);
        }
        let name = WideCString::new(name).expect("valid name widestring")
                                         .to_string_lossy();
        let domain = WideCString::new(domain).expect("valid domain widestring")
                                             .to_string_lossy();
        Some(Account { name,
                       system_name: None,
                       domain,
                       account_type: sid_type,
                       sid })
    }

    pub fn machine() -> Account {
        let name = env::var("COMPUTERNAME").expect("COMPUTERNAME env var");
        Account::from_name(&name).expect("computer account")
    }

    pub fn built_in_administrators() -> Account {
        // Use the SID string constant for the built in administrators
        // https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings
        Account::from_sid("BA").expect("local administrator account")
    }

    pub fn local_system() -> Account {
        // Use the SID string constant for the local system
        // https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings
        Account::from_sid("SY").expect("local system account")
    }
}

fn lookup_account(name: &str, system_name: Option<String>) -> Option<Account> {
    // if this is a machine account, strip the terminating '$'
    // LookupAccountName will return the sid of the computer account
    // given the computer name. Windows forbids usernames to match the
    // computer name
    let stripped_name =
        if name.to_lowercase() == (env::var("COMPUTERNAME").unwrap().to_lowercase() + "$") {
            &name[..name.len() - 1]
        } else {
            name
        };
    let mut sid_size: u32 = 0;
    let mut domain_size: u32 = 0;
    let wide = WideCString::from_str(stripped_name).unwrap();
    unsafe {
        LookupAccountNameW(null_mut(),
                           wide.as_ptr(),
                           null_mut(),
                           &mut sid_size as LPDWORD,
                           null_mut(),
                           &mut domain_size as LPDWORD,
                           null_mut())
    };
    match Error::last_os_error().raw_os_error().unwrap() as u32 {
        ERROR_INSUFFICIENT_BUFFER => {}
        ERROR_NONE_MAPPED => return None,
        _ => {
            debug!("Error while looking up account for {}: {}",
                   name,
                   Error::last_os_error());
            return None;
        }
    }

    let mut sid: Vec<u8> = Vec::with_capacity(sid_size as usize);
    let mut domain: Vec<u16> = Vec::with_capacity(domain_size as usize);
    let mut sid_type: SID_NAME_USE = 0 as SID_NAME_USE;

    let ret = unsafe {
        LookupAccountNameW(null_mut(),
                           wide.as_ptr(),
                           sid.as_mut_ptr() as PSID,
                           &mut sid_size as LPDWORD,
                           domain.as_mut_ptr(),
                           &mut domain_size as LPDWORD,
                           &mut sid_type as PSID_NAME_USE)
    };
    if ret == 0 {
        error!("Failed to retrieve SID for {}: {}",
               name,
               Error::last_os_error());
        return None;
    }
    unsafe {
        domain.set_len(domain_size as usize);
        sid.set_len(sid_size as usize);
    }
    let domain_str = WideCString::new(domain).unwrap().to_string_lossy();
    Some(Account { name: name.to_string(),
                   system_name,
                   domain: domain_str,
                   account_type: sid_type,
                   sid: Sid { raw: sid } })
}

#[cfg(test)]
mod tests {
    use std::env;

    use winapi::um::winnt::{SidTypeUser,
                            SidTypeWellKnownGroup};

    use super::*;

    #[test]
    fn real_account_returns_some() {
        assert_eq!(Account::from_name("Administrator").is_some(), true)
    }

    #[test]
    fn bogus_account_returns_none() { assert_eq!(Account::from_name("bogus").is_none(), true) }

    #[test]
    fn user_account_returns_user_type() {
        let acct_type = Account::from_name("Administrator").unwrap().account_type;
        assert_eq!(acct_type, SidTypeUser)
    }

    #[test]
    fn local_user_account_returns_local_machine_as_domain() {
        let acct_domain = Account::from_name("Administrator").unwrap().domain;
        assert_eq!(acct_domain, env::var("COMPUTERNAME").unwrap())
    }

    #[test]
    fn well_known_group_account_returns_correct_type() {
        let acct_type = Account::from_name("Everyone").unwrap().account_type;
        assert_eq!(acct_type, SidTypeWellKnownGroup)
    }

    #[test]
    fn well_known_group_account_has_well_known_sid() {
        let sid = Account::from_name("Everyone").unwrap().sid;
        assert_eq!(sid.to_string().unwrap(), "S-1-1-0")
    }

    #[test]
    fn mixing_case_returns_same_account() {
        let current_user = env::var("USERNAME").unwrap();
        let lower_sid = Account::from_name(current_user.to_lowercase().as_str()).unwrap()
                                                                                .sid;
        let upper_sid = Account::from_name(current_user.to_uppercase().as_str()).unwrap()
                                                                                .sid;
        assert_eq!(lower_sid.to_string().unwrap(),
                   upper_sid.to_string().unwrap())
    }

    #[test]
    fn test_built_in_accounts() {
        // Check the machine account
        let machine = Account::machine();
        assert_eq!(machine.name, machine.domain);
        let machine_sid = machine.sid.to_string().expect("sid to string");
        assert!(machine_sid.starts_with("S-1-5-21-"));

        // Check the local administrator account
        let administrator = Account::from_sid("LA").expect("Administrator account");
        assert_eq!(administrator.name, "Administrator");
        assert_eq!(administrator.domain, machine.name);
        assert_eq!(administrator.sid.to_string().expect("sid to string"),
                   format!("{}-500", machine_sid));

        // Check the built in administrators account
        let administrators = Account::built_in_administrators();
        assert_eq!(administrators.name, "Administrators");
        assert_eq!(administrators.domain, "BUILTIN");
        assert_eq!(administrators.sid.to_string().expect("sid to string"),
                   "S-1-5-32-544");

        // The built in administrators account should be identical to the account we get looking up
        // the "Administrators" account by name
        let a = Account::from_name("Administrators").expect("Administrators account");
        assert_eq!(administrators.name, a.name);
        assert_eq!(administrators.system_name, a.system_name);
        assert_eq!(administrators.domain, a.domain);
        assert_eq!(administrators.account_type, a.account_type);
        assert_eq!(administrators.sid.raw, a.sid.raw);

        // Check the system account
        let system = Account::local_system();
        assert_eq!(system.name, "SYSTEM");
        assert_eq!(system.domain, "NT AUTHORITY");
        assert_eq!(system.sid.to_string().expect("sid to string"), "S-1-5-18");

        // The system account should be identical to the account we get looking up the
        // "SYSTEM" account by name
        let s = Account::from_name("SYSTEM").expect("SYSTEM account");
        assert_eq!(system.name, s.name);
        assert_eq!(system.system_name, s.system_name);
        assert_eq!(system.domain, s.domain);
        assert_eq!(system.account_type, s.account_type);
        assert_eq!(system.sid.raw, s.sid.raw);
    }
}
<commit_msg>Check return code before calling last_os_error<commit_after>use std::{env,
          io::Error,
          ptr::{null,
                null_mut}};

use widestring::WideCString;
use winapi::{shared::{minwindef::{BOOL,
                                  LPDWORD},
                      ntdef::LPCWSTR,
                      winerror::*},
             um::{winbase::LookupAccountSidW,
                  winnt::{PSID,
                          PSID_NAME_USE,
                          SID_NAME_USE}}};

use super::sid::Sid;

extern "system" {
    fn LookupAccountNameW(lpSystemName: LPCWSTR,
                          lpAccountName: LPCWSTR,
                          Sid: PSID,
                          cbSid: LPDWORD,
                          ReferencedDomainName: LPCWSTR,
                          cchReferencedDomainName: LPDWORD,
                          peUse: PSID_NAME_USE)
                          -> BOOL;
}

pub struct Account {
    pub name:         String,
    pub system_name:  Option<String>,
    pub domain:       String,
    pub account_type: SID_NAME_USE,
    pub sid:          Sid,
}

impl Account {
    pub fn from_name(name: &str) -> Option<Account> { lookup_account(name, None) }

    pub fn from_name_and_system(name: &str, system_name: &str) -> Option<Account> {
        lookup_account(name, Some(system_name.to_string()))
    }

    pub fn from_sid(sid: &str) -> Option<Account> {
        let sid = if let Ok(sid) = Sid::from_str(sid) {
            sid
        } else {
            return None;
        };
        let mut name_size: u32 = 0;
        let mut domain_size: u32 = 0;
        let ret = unsafe {
            LookupAccountSidW(null(),
                              sid.raw.as_ptr() as *mut _,
                              null_mut(),
                              &mut name_size as LPDWORD,
                              null_mut(),
                              &mut domain_size as LPDWORD,
                              null_mut())
        };
        if ret == 0 {
            match Error::last_os_error().raw_os_error().unwrap() as u32 {
                ERROR_INSUFFICIENT_BUFFER => {}
                ERROR_NONE_MAPPED => return None,
                _ => {
                    debug!("Error while looking up account for {}: {}",
                           sid.to_string().expect("to convert sid to string"),
                           Error::last_os_error());
                    return None;
                }
            }
        }

        let mut name: Vec<u16> = Vec::with_capacity(name_size as usize);
        let mut domain: Vec<u16> = Vec::with_capacity(domain_size as usize);
        let mut sid_type = 0;

        let ret = unsafe {
            LookupAccountSidW(null(),
                              sid.raw.as_ptr() as *mut _,
                              name.as_mut_ptr(),
                              &mut name_size as LPDWORD,
                              domain.as_mut_ptr(),
                              &mut domain_size as LPDWORD,
                              &mut sid_type as PSID_NAME_USE)
        };
        if ret == 0 {
            error!("Failed to retrieve name for {}: {}",
                   sid.to_string().expect("to convert sid to string"),
                   Error::last_os_error());
            return None;
        }
        unsafe {
            name.set_len(name_size as usize);
            domain.set_len(domain_size as usize);
        }
        let name = WideCString::new(name).expect("valid name widestring")
                                         .to_string_lossy();
        let domain = WideCString::new(domain).expect("valid domain widestring")
                                             .to_string_lossy();
        Some(Account { name,
                       system_name: None,
                       domain,
                       account_type: sid_type,
                       sid })
    }

    pub fn machine() -> Account {
        let name = env::var("COMPUTERNAME").expect("COMPUTERNAME env var");
        Account::from_name(&name).expect("computer account")
    }

    pub fn built_in_administrators() -> Account {
        // Use the SID string constant for the built in administrators
        // https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings
        Account::from_sid("BA").expect("local administrator account")
    }

    pub fn local_system() -> Account {
        // Use the SID string constant for the local system
        // https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings
        Account::from_sid("SY").expect("local system account")
    }
}

fn lookup_account(name: &str, system_name: Option<String>) -> Option<Account> {
    // if this is a machine account, strip the terminating '$'
    // LookupAccountName will return the sid of the computer account
    // given the computer name. Windows forbids usernames to match the
    // computer name
    let stripped_name =
        if name.to_lowercase() == (env::var("COMPUTERNAME").unwrap().to_lowercase() + "$") {
            &name[..name.len() - 1]
        } else {
            name
        };
    let mut sid_size: u32 = 0;
    let mut domain_size: u32 = 0;
    let wide = WideCString::from_str(stripped_name).unwrap();
    let ret = unsafe {
        LookupAccountNameW(null_mut(),
                           wide.as_ptr(),
                           null_mut(),
                           &mut sid_size as LPDWORD,
                           null_mut(),
                           &mut domain_size as LPDWORD,
                           null_mut())
    };
    if ret == 0 {
        match Error::last_os_error().raw_os_error().unwrap() as u32 {
            ERROR_INSUFFICIENT_BUFFER => {}
            ERROR_NONE_MAPPED => return None,
            _ => {
                debug!("Error while looking up account for {}: {}",
                       name,
                       Error::last_os_error());
                return None;
            }
        }
    }

    let mut sid: Vec<u8> = Vec::with_capacity(sid_size as usize);
    let mut domain: Vec<u16> = Vec::with_capacity(domain_size as usize);
    let mut sid_type: SID_NAME_USE = 0 as SID_NAME_USE;

    let ret = unsafe {
        LookupAccountNameW(null_mut(),
                           wide.as_ptr(),
                           sid.as_mut_ptr() as PSID,
                           &mut sid_size as LPDWORD,
                           domain.as_mut_ptr(),
                           &mut domain_size as LPDWORD,
                           &mut sid_type as PSID_NAME_USE)
    };
    if ret == 0 {
        error!("Failed to retrieve SID for {}: {}",
               name,
               Error::last_os_error());
        return None;
    }
    unsafe {
        domain.set_len(domain_size as usize);
        sid.set_len(sid_size as usize);
    }
    let domain_str = WideCString::new(domain).unwrap().to_string_lossy();
    Some(Account { name: name.to_string(),
                   system_name,
                   domain: domain_str,
                   account_type: sid_type,
                   sid: Sid { raw: sid } })
}

#[cfg(test)]
mod tests {
    use std::env;

    use winapi::um::winnt::{SidTypeUser,
                            SidTypeWellKnownGroup};

    use super::*;

    #[test]
    fn real_account_returns_some() {
        assert_eq!(Account::from_name("Administrator").is_some(), true)
    }

    #[test]
    fn bogus_account_returns_none() { assert_eq!(Account::from_name("bogus").is_none(), true) }

    #[test]
    fn user_account_returns_user_type() {
        let acct_type = Account::from_name("Administrator").unwrap().account_type;
        assert_eq!(acct_type, SidTypeUser)
    }

    #[test]
    fn local_user_account_returns_local_machine_as_domain() {
        let acct_domain = Account::from_name("Administrator").unwrap().domain;
        assert_eq!(acct_domain, env::var("COMPUTERNAME").unwrap())
    }

    #[test]
    fn well_known_group_account_returns_correct_type() {
        let acct_type = Account::from_name("Everyone").unwrap().account_type;
        assert_eq!(acct_type, SidTypeWellKnownGroup)
    }

    #[test]
    fn well_known_group_account_has_well_known_sid() {
        let sid = Account::from_name("Everyone").unwrap().sid;
        assert_eq!(sid.to_string().unwrap(), "S-1-1-0")
    }

    #[test]
    fn mixing_case_returns_same_account() {
        let current_user = env::var("USERNAME").unwrap();
        let lower_sid = Account::from_name(current_user.to_lowercase().as_str()).unwrap()
                                                                                .sid;
        let upper_sid = Account::from_name(current_user.to_uppercase().as_str()).unwrap()
                                                                                .sid;
        assert_eq!(lower_sid.to_string().unwrap(),
                   upper_sid.to_string().unwrap())
    }

    #[test]
    fn test_built_in_accounts() {
        // Check the machine account
        let machine = Account::machine();
        assert_eq!(machine.name, machine.domain);
        let machine_sid = machine.sid.to_string().expect("sid to string");
        assert!(machine_sid.starts_with("S-1-5-21-"));

        // Check the local administrator account
        let administrator = Account::from_sid("LA").expect("Administrator account");
        assert_eq!(administrator.name, "Administrator");
        assert_eq!(administrator.domain, machine.name);
        assert_eq!(administrator.sid.to_string().expect("sid to string"),
                   format!("{}-500", machine_sid));

        // Check the built in administrators account
        let administrators = Account::built_in_administrators();
        assert_eq!(administrators.name, "Administrators");
        assert_eq!(administrators.domain, "BUILTIN");
        assert_eq!(administrators.sid.to_string().expect("sid to string"),
                   "S-1-5-32-544");

        // The built in administrators account should be identical to the account we get looking up
        // the "Administrators" account by name
        let a = Account::from_name("Administrators").expect("Administrators account");
        assert_eq!(administrators.name, a.name);
        assert_eq!(administrators.system_name, a.system_name);
        assert_eq!(administrators.domain, a.domain);
        assert_eq!(administrators.account_type, a.account_type);
        assert_eq!(administrators.sid.raw, a.sid.raw);

        // Check the system account
        let system = Account::local_system();
        assert_eq!(system.name, "SYSTEM");
        assert_eq!(system.domain, "NT AUTHORITY");
        assert_eq!(system.sid.to_string().expect("sid to string"), "S-1-5-18");

        // The system account should be identical to the account we get looking up the
        // "SYSTEM" account by name
        let s = Account::from_name("SYSTEM").expect("SYSTEM account");
        assert_eq!(system.name, s.name);
        assert_eq!(system.system_name, s.system_name);
        assert_eq!(system.domain, s.domain);
        assert_eq!(system.account_type, s.account_type);
        assert_eq!(system.sid.raw, s.sid.raw);
    }
}
<|endoftext|>