<commit_before>/// A TOML [1] configuration file parser
///
/// Copyright (c) 2014 by Michael Neumann
///
/// [1]: https://github.com/mojombo/toml

use std::io::Buffer;
use std::hashmap::HashMap;
use std::char;
use std::io::mem::MemReader;

#[deriving(ToStr,Clone)]
pub enum Value {
    True,
    False,
    Unsigned(u64),
    Integer(i64),
    Float(f64),
    String(~str),
    Array(~[Value]),
    Datetime(u16,u8,u8,u8,u8,u8),
    Table(~[Value]),
    Map(HashMap<~str, Value>)
}

pub trait Visitor {
    fn section(&mut self, name: ~str, is_array: bool) -> bool;
    fn pair(&mut self, key: ~str, val: Value) -> bool;
}

pub struct ValueBuilder {
    root: HashMap<~str, Value>,
    current_section: ~str,
    section_is_array: bool
}

impl ValueBuilder {
    pub fn new() -> ValueBuilder {
        ValueBuilder { root: HashMap::new(), current_section: ~"", section_is_array: false }
    }

    fn insert(&mut self, path: &str, value: Value) -> bool {
        let path: ~[&str] = path.split_str(".").collect();
        assert!(path.len() > 0);

        return ValueBuilder::ins(path, &mut self.root, value);
    }

    fn ins(path: &[&str], ht: &mut HashMap<~str, Value>, val: Value) -> bool {
        assert!(path.len() > 0);

        let head = path.head().to_owned();

        if path.len() == 1 {
            let ok = ht.insert(head, val);
            if !ok {
                debug!("Duplicate key");
            }
            return ok;
        }
        else {
            let m = ht.find_or_insert(head, Map(HashMap::new())); // Optimize
            match *m {
                Map(ref mut map) => {
                    return ValueBuilder::ins(path.slice_from(1), map, val);
                }
                _ => {
                    debug!("Wrong type/duplicate key");
                    return false;
                }
            }
        }
    }

    pub fn get_root<'a>(&'a self) -> &'a HashMap<~str, Value> {
        return &self.root;
    }
}

impl Visitor for ValueBuilder {
    fn section(&mut self, name: ~str, is_array: bool) -> bool {
        let ok = self.insert(name, Map(HashMap::new()));
        if !ok {
            debug!("Duplicate key: {}", name);
        }

        self.current_section = name;
        self.section_is_array = is_array; // XXX: not implemented yet

        return ok;
    }

    fn pair(&mut self, key: ~str, val: Value) -> bool {
        if self.current_section.len() == 0 {
            let ok = self.insert(key, val);
            if !ok {
                debug!("Duplicate key: {}", key);
            }
            return ok;
        } else {
            let path = self.current_section + "." + key;
            let ok = self.insert(path, val);
            if !ok {
                debug!("Duplicate key: {}", path);
            }
            return ok;
        }
    }
}

pub struct Parser<'a, BUF> {
    rd: &'a mut BUF,
    current_char: Option<char>
}

impl<'a, BUF: Buffer> Parser<'a, BUF> {
    pub fn new(rd: &'a mut BUF) -> Parser<'a, BUF> {
        let ch = rd.read_char();
        Parser { rd: rd, current_char: ch }
    }

    pub fn parse_from_buffer(rd: &mut BUF) -> Option<Value> {
        let mut builder = ValueBuilder::new();
        let mut parser = Parser::new(rd);
        if parser.parse(&mut builder) {
            return Some(Map(builder.get_root().clone()));
        } else {
            return None;
        }
    }

    pub fn parse_from_bytes(bytes: ~[u8]) -> Option<Value> {
        let mut rd = MemReader::new(bytes);
        return Parser::parse_from_buffer(&mut rd);
    }

    fn advance(&mut self) {
        self.current_char = self.rd.read_char();
    }

    fn ch(&self) -> Option<char> {
        return self.current_char;
    }

    fn eos(&self) -> bool {
        return self.current_char.is_none();
    }

    fn advance_if(&mut self, c: char) -> bool {
        match self.ch() {
            Some(ch) if ch == c => {
               self.advance();
               true
            }
            _ => {
                false
            }
        }
    }

    fn read_digit(&mut self, radix: uint) -> Option<u8> {
        if self.eos() { return None }
        match char::to_digit(self.ch().unwrap(), radix) {
            Some(n) => {
                self.advance();
                Some(n as u8)
            }
            None => { None }
        }
    }

    fn read_two_digits(&mut self) -> Option<u8> {
        let d1 = self.read_digit(10);
        let d2 = self.read_digit(10);
        match (d1, d2) {
            (Some(d1), Some(d2)) => Some(d1*10+d2),
            _ => None
        }
    }

    fn read_digits(&mut self) -> (Option<u64>, uint) {
        let mut num: u64;
        match self.read_digit(10) {
            Some(n) => { num = n as u64; }
            None => { return (None, 0) }
        }
        let mut ndigits = 1;
        loop {
            match self.read_digit(10) {
                Some(n) => {
                    // XXX: check range
                    num = num * 10 + (n as u64);
                    ndigits += 1;
                }
                None => {
                    return (Some(num), ndigits)
                }
            }
        }
    }

    // allows a single "."
    fn read_float_mantissa(&mut self) -> f64 {
        let mut num: f64 = 0.0;
        let mut div: f64 = 10.0;

        loop {
            match self.read_digit(10) {
                Some(n) => {
                    num = num + (n as f64)/div;
                    div = div * 10.0;
                }
                None => {
                    return num;
                }
            }
        }
    }

    fn parse_value(&mut self) -> Option<Value> {
        self.skip_whitespaces();

        if self.eos() { return None }
        match self.ch().unwrap() {
            '-' => {
                self.advance();
                match self.read_digits() {
                    (Some(n), _) => {
                        if self.ch() == Some('.') {
                            // floating point
                            self.advance();
                            let num = self.read_float_mantissa();
                            let num = (n as f64) + num;
                            return Some(Float(-num));
                        }
                        else {
                            match n.to_i64() {
                                Some(i) => return Some(Integer(-i)),
                                None => return None // XXX: Use Result
                            }
                        }
                    }
                    (None, _) => {
                        return None
                    }
                }
            }
            '0' .. '9' => {
                match self.read_digits() {
                    (Some(n), ndigits) => {
                        match self.ch() {
                            Some('.') => {
                                // floating point
                                self.advance();
                                let num = self.read_float_mantissa();
                                let num = (n as f64) + num;
                                return Some(Float(num));
                            }
                            Some('-') => {
                                if ndigits != 4 {
                                    debug!("Invalid Datetime");
                                    return None;
                                }
                                self.advance();

                                let year = n;

                                let month = self.read_two_digits();
                                if month.is_none() || !self.advance_if('-') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let day = self.read_two_digits();
                                if day.is_none() || !self.advance_if('T'){
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let hour = self.read_two_digits();
                                if hour.is_none() || !self.advance_if(':') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let min = self.read_two_digits();
                                if min.is_none() || !self.advance_if(':') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let sec = self.read_two_digits();
                                if sec.is_none() || !self.advance_if('Z') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                match (year, month, day, hour, min, sec) {
                                    (y, Some(m), Some(d),
                                     Some(h), Some(min), Some(s))
                                    if m > 0 && m <= 12 && d > 0 && d <= 31 &&
                                       h <= 24 && min <= 60 && s <= 60 => {
                                        return Some(Datetime(y as u16,m,d,h,min,s))
                                    }
                                    _ => {
                                        debug!("Invalid Datetime range");
                                        return None;
                                    }
                                }
                            }
                            _ => {
                                return Some(Unsigned(n))
                            }
                        }
                    }
                    (None, _) => {
                        assert!(false);
                        return None
                    }
                }
            }
            't' => {
                self.advance();
                if self.advance_if('r') &&
                   self.advance_if('u') &&
                   self.advance_if('e') {
                    return Some(True)
                } else {
                    return None
                }
            }
            'f' => {
                self.advance();
                if self.advance_if('a') &&
                   self.advance_if('l') &&
                   self.advance_if('s') &&
                   self.advance_if('e') {
                    return Some(True)
                } else {
                    return None
                }
            }
            '[' => {
                self.advance();
                let mut arr = ~[];
                loop {
                    match self.parse_value() {
                        Some(val) => {
                            arr.push(val);
                        }
                        None => {
                            break;
                        }
                    }

                    self.skip_whitespaces_and_comments();
                    if !self.advance_if(',') { break }
                }
                self.skip_whitespaces_and_comments();
                if self.advance_if(']') {
                    return Some(Array(arr));
                } else {
                    return None;
                }
            }
            '"' => {
                match self.parse_string() {
                    Some(str) => { return Some(String(str)) }
                    None => { return None }
                }
            }
            _ => { return None }
        }
    }

    fn parse_string(&mut self) -> Option<~str> {
        if !self.advance_if('"') { return None }

        let mut str = ~"";
        loop {
            if self.ch().is_none() { return None }
            match self.ch().unwrap() {
                '\r' | '\n' | '\u000C' | '\u0008' => { return None }
                '\\' => {
                    self.advance();
                    if self.ch().is_none() { return None }
                    match self.ch().unwrap() {
                        'b' => { str.push_char('\u0008'); self.advance() },
                        't' => { str.push_char('\t'); self.advance() },
                        'n' => { str.push_char('\n'); self.advance() },
                        'f' => { str.push_char('\u000C'); self.advance() },
                        'r' => { str.push_char('\r'); self.advance() },
                        '"' => { str.push_char('"'); self.advance() },
                        '/' => { str.push_char('/'); self.advance() },
                        '\\' => { str.push_char('\\'); self.advance() },
                        'u' => {
                            self.advance();
                            let d1 = self.read_digit(16);
                            let d2 = self.read_digit(16);
                            let d3 = self.read_digit(16);
                            let d4 = self.read_digit(16);
                            match (d1, d2, d3, d4) {
                                (Some(d1), Some(d2), Some(d3), Some(d4)) => {
                                    // XXX: how to construct an UTF character
                                    let ch = (((((d1 as u32 << 8) | d2 as u32) << 8) | d3 as u32) << 8) | d4 as u32;
                                    match char::from_u32(ch) {
                                        Some(ch) => {
                                            str.push_char(ch);
                                        }
                                        None => {
                                            return None;
                                        }
                                    }
                                }
                                _ => return None
                            }
                        }
                        _ => { return None }
                    }
                }
                '"' => {
                    self.advance();
                    return Some(str);
                }
                c => {
                    str.push_char(c);
                    self.advance();
                }
            }
        }
    }

    fn read_token(&mut self, f: |char| -> bool) -> ~str {
        let mut token = ~"";
        loop {
            match self.ch() {
                Some(ch) => {
                    if f(ch) { token.push_char(ch) }
                    else { break }
                }
                None => { break }
            }
            self.advance();
        }

        return token;
    }

    fn parse_section_identifier(&mut self) -> ~str {
        self.read_token(|ch| {
            match ch {
                'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' => true,
                _ => false
            }
        })
    }

    fn skip_whitespaces(&mut self) {
        loop {
            match self.ch() {
                Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
                    self.advance();
                }
                _ => { break }
            }
        }
    }

    fn skip_whitespaces_and_comments(&mut self) {
        loop {
            match self.ch() {
                Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
                    self.advance();
                }
                Some('#') => {
                    self.skip_comment();
                }
                _ => { break }
            }
        }
    }

    fn skip_comment(&mut self) {
        assert!(self.ch() == Some('#'));
        // skip to end of line
        loop {
            self.advance();
            match self.ch() {
                Some('\n') => { break }
                None => { return }
                _ => { /* skip */ }
            }
        }
        self.advance();
    }

    pub fn parse<V: Visitor>(&mut self, visitor: &mut V) -> bool {
        loop {
            if self.eos() { return true }
            match self.ch().unwrap() {
                // ignore whitespace
                '\r' | '\n' | ' ' | '\t' => {
                    self.advance();
                }

                // comment
                '#' => {
                    self.skip_comment();
                }

                // section
                '[' => {
                    self.advance();
                    let mut double_section = false;
                    match self.ch() {
                        Some('[') => {
                            double_section = true;
                            self.advance();
                        }
                        _ => {}
                    }

                    let section_name = self.parse_section_identifier();

                    if !self.advance_if(']') { return false }
                    if double_section {
                        if !self.advance_if(']') { return false }
                    }

                    visitor.section(section_name, double_section);
                }

                // identifier
                'a' .. 'z' | 'A' .. 'Z' | '_' => {

                    let ident = self.read_token(|ch| {
                        match ch {
                            'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' => true,
                            _ => false
                        }
                    });

                    self.skip_whitespaces();

                    if !self.advance_if('=') { return false } // assign wanted

                    match self.parse_value() {
                        Some(val) => { visitor.pair(ident, val); }
                        None => { return false; }
                    }
                    // do not advance!
                }

                _ => { return false }
            } /* end match */
        }

        assert!(false);
    }
}
<commit_msg>Rename Map -> Table. Table -> TableArray<commit_after>/// A TOML [1] configuration file parser
///
/// Copyright (c) 2014 by Michael Neumann
///
/// [1]: https://github.com/mojombo/toml

use std::io::Buffer;
use std::hashmap::HashMap;
use std::char;
use std::io::mem::MemReader;

#[deriving(ToStr,Clone)]
pub enum Value {
    True,
    False,
    Unsigned(u64),
    Integer(i64),
    Float(f64),
    String(~str),
    Array(~[Value]),
    Datetime(u16,u8,u8,u8,u8,u8),
    TableArray(~[HashMap<~str, Value>]),
    Table(HashMap<~str, Value>)
}

pub trait Visitor {
    fn section(&mut self, name: ~str, is_array: bool) -> bool;
    fn pair(&mut self, key: ~str, val: Value) -> bool;
}

pub struct ValueBuilder {
    root: HashMap<~str, Value>,
    current_section: ~str,
    section_is_array: bool
}

impl ValueBuilder {
    pub fn new() -> ValueBuilder {
        ValueBuilder { root: HashMap::new(), current_section: ~"", section_is_array: false }
    }

    fn insert(&mut self, path: &str, value: Value) -> bool {
        let path: ~[&str] = path.split_str(".").collect();
        assert!(path.len() > 0);

        return ValueBuilder::ins(path, &mut self.root, value);
    }

    fn ins(path: &[&str], ht: &mut HashMap<~str, Value>, val: Value) -> bool {
        assert!(path.len() > 0);

        let head = path.head().to_owned();

        if path.len() == 1 {
            let ok = ht.insert(head, val);
            if !ok {
                debug!("Duplicate key");
            }
            return ok;
        }
        else {
            let m = ht.find_or_insert(head, Table(HashMap::new())); // Optimize
            match *m {
                Table(ref mut map) => {
                    return ValueBuilder::ins(path.slice_from(1), map, val);
                }
                _ => {
                    debug!("Wrong type/duplicate key");
                    return false;
                }
            }
        }
    }

    pub fn get_root<'a>(&'a self) -> &'a HashMap<~str, Value> {
        return &self.root;
    }
}

impl Visitor for ValueBuilder {
    fn section(&mut self, name: ~str, is_array: bool) -> bool {
        let ok = self.insert(name, Table(HashMap::new()));
        if !ok {
            debug!("Duplicate key: {}", name);
        }

        self.current_section = name;
        self.section_is_array = is_array; // XXX: not implemented yet

        return ok;
    }

    fn pair(&mut self, key: ~str, val: Value) -> bool {
        if self.current_section.len() == 0 {
            let ok = self.insert(key, val);
            if !ok {
                debug!("Duplicate key: {}", key);
            }
            return ok;
        } else {
            let path = self.current_section + "." + key;
            let ok = self.insert(path, val);
            if !ok {
                debug!("Duplicate key: {}", path);
            }
            return ok;
        }
    }
}

pub struct Parser<'a, BUF> {
    rd: &'a mut BUF,
    current_char: Option<char>
}

impl<'a, BUF: Buffer> Parser<'a, BUF> {
    pub fn new(rd: &'a mut BUF) -> Parser<'a, BUF> {
        let ch = rd.read_char();
        Parser { rd: rd, current_char: ch }
    }

    pub fn parse_from_buffer(rd: &mut BUF) -> Option<Value> {
        let mut builder = ValueBuilder::new();
        let mut parser = Parser::new(rd);
        if parser.parse(&mut builder) {
            return Some(Table(builder.get_root().clone()));
        } else {
            return None;
        }
    }

    pub fn parse_from_bytes(bytes: ~[u8]) -> Option<Value> {
        let mut rd = MemReader::new(bytes);
        return Parser::parse_from_buffer(&mut rd);
    }

    fn advance(&mut self) {
        self.current_char = self.rd.read_char();
    }

    fn ch(&self) -> Option<char> {
        return self.current_char;
    }

    fn eos(&self) -> bool {
        return self.current_char.is_none();
    }

    fn advance_if(&mut self, c: char) -> bool {
        match self.ch() {
            Some(ch) if ch == c => {
               self.advance();
               true
            }
            _ => {
                false
            }
        }
    }

    fn read_digit(&mut self, radix: uint) -> Option<u8> {
        if self.eos() { return None }
        match char::to_digit(self.ch().unwrap(), radix) {
            Some(n) => {
                self.advance();
                Some(n as u8)
            }
            None => { None }
        }
    }

    fn read_two_digits(&mut self) -> Option<u8> {
        let d1 = self.read_digit(10);
        let d2 = self.read_digit(10);
        match (d1, d2) {
            (Some(d1), Some(d2)) => Some(d1*10+d2),
            _ => None
        }
    }

    fn read_digits(&mut self) -> (Option<u64>, uint) {
        let mut num: u64;
        match self.read_digit(10) {
            Some(n) => { num = n as u64; }
            None => { return (None, 0) }
        }
        let mut ndigits = 1;
        loop {
            match self.read_digit(10) {
                Some(n) => {
                    // XXX: check range
                    num = num * 10 + (n as u64);
                    ndigits += 1;
                }
                None => {
                    return (Some(num), ndigits)
                }
            }
        }
    }

    // allows a single "."
    fn read_float_mantissa(&mut self) -> f64 {
        let mut num: f64 = 0.0;
        let mut div: f64 = 10.0;

        loop {
            match self.read_digit(10) {
                Some(n) => {
                    num = num + (n as f64)/div;
                    div = div * 10.0;
                }
                None => {
                    return num;
                }
            }
        }
    }

    fn parse_value(&mut self) -> Option<Value> {
        self.skip_whitespaces();

        if self.eos() { return None }
        match self.ch().unwrap() {
            '-' => {
                self.advance();
                match self.read_digits() {
                    (Some(n), _) => {
                        if self.ch() == Some('.') {
                            // floating point
                            self.advance();
                            let num = self.read_float_mantissa();
                            let num = (n as f64) + num;
                            return Some(Float(-num));
                        }
                        else {
                            match n.to_i64() {
                                Some(i) => return Some(Integer(-i)),
                                None => return None // XXX: Use Result
                            }
                        }
                    }
                    (None, _) => {
                        return None
                    }
                }
            }
            '0' .. '9' => {
                match self.read_digits() {
                    (Some(n), ndigits) => {
                        match self.ch() {
                            Some('.') => {
                                // floating point
                                self.advance();
                                let num = self.read_float_mantissa();
                                let num = (n as f64) + num;
                                return Some(Float(num));
                            }
                            Some('-') => {
                                if ndigits != 4 {
                                    debug!("Invalid Datetime");
                                    return None;
                                }
                                self.advance();

                                let year = n;

                                let month = self.read_two_digits();
                                if month.is_none() || !self.advance_if('-') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let day = self.read_two_digits();
                                if day.is_none() || !self.advance_if('T'){
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let hour = self.read_two_digits();
                                if hour.is_none() || !self.advance_if(':') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let min = self.read_two_digits();
                                if min.is_none() || !self.advance_if(':') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                let sec = self.read_two_digits();
                                if sec.is_none() || !self.advance_if('Z') {
                                    debug!("Invalid Datetime");
                                    return None;
                                }

                                match (year, month, day, hour, min, sec) {
                                    (y, Some(m), Some(d),
                                     Some(h), Some(min), Some(s))
                                    if m > 0 && m <= 12 && d > 0 && d <= 31 &&
                                       h <= 24 && min <= 60 && s <= 60 => {
                                        return Some(Datetime(y as u16,m,d,h,min,s))
                                    }
                                    _ => {
                                        debug!("Invalid Datetime range");
                                        return None;
                                    }
                                }
                            }
                            _ => {
                                return Some(Unsigned(n))
                            }
                        }
                    }
                    (None, _) => {
                        assert!(false);
                        return None
                    }
                }
            }
            't' => {
                self.advance();
                if self.advance_if('r') &&
                   self.advance_if('u') &&
                   self.advance_if('e') {
                    return Some(True)
                } else {
                    return None
                }
            }
            'f' => {
                self.advance();
                if self.advance_if('a') &&
                   self.advance_if('l') &&
                   self.advance_if('s') &&
                   self.advance_if('e') {
                    return Some(True)
                } else {
                    return None
                }
            }
            '[' => {
                self.advance();
                let mut arr = ~[];
                loop {
                    match self.parse_value() {
                        Some(val) => {
                            arr.push(val);
                        }
                        None => {
                            break;
                        }
                    }

                    self.skip_whitespaces_and_comments();
                    if !self.advance_if(',') { break }
                }
                self.skip_whitespaces_and_comments();
                if self.advance_if(']') {
                    return Some(Array(arr));
                } else {
                    return None;
                }
            }
            '"' => {
                match self.parse_string() {
                    Some(str) => { return Some(String(str)) }
                    None => { return None }
                }
            }
            _ => { return None }
        }
    }

    fn parse_string(&mut self) -> Option<~str> {
        if !self.advance_if('"') { return None }

        let mut str = ~"";
        loop {
            if self.ch().is_none() { return None }
            match self.ch().unwrap() {
                '\r' | '\n' | '\u000C' | '\u0008' => { return None }
                '\\' => {
                    self.advance();
                    if self.ch().is_none() { return None }
                    match self.ch().unwrap() {
                        'b' => { str.push_char('\u0008'); self.advance() },
                        't' => { str.push_char('\t'); self.advance() },
                        'n' => { str.push_char('\n'); self.advance() },
                        'f' => { str.push_char('\u000C'); self.advance() },
                        'r' => { str.push_char('\r'); self.advance() },
                        '"' => { str.push_char('"'); self.advance() },
                        '/' => { str.push_char('/'); self.advance() },
                        '\\' => { str.push_char('\\'); self.advance() },
                        'u' => {
                            self.advance();
                            let d1 = self.read_digit(16);
                            let d2 = self.read_digit(16);
                            let d3 = self.read_digit(16);
                            let d4 = self.read_digit(16);
                            match (d1, d2, d3, d4) {
                                (Some(d1), Some(d2), Some(d3), Some(d4)) => {
                                    // XXX: how to construct an UTF character
                                    let ch = (((((d1 as u32 << 8) | d2 as u32) << 8) | d3 as u32) << 8) | d4 as u32;
                                    match char::from_u32(ch) {
                                        Some(ch) => {
                                            str.push_char(ch);
                                        }
                                        None => {
                                            return None;
                                        }
                                    }
                                }
                                _ => return None
                            }
                        }
                        _ => { return None }
                    }
                }
                '"' => {
                    self.advance();
                    return Some(str);
                }
                c => {
                    str.push_char(c);
                    self.advance();
                }
            }
        }
    }

    fn read_token(&mut self, f: |char| -> bool) -> ~str {
        let mut token = ~"";
        loop {
            match self.ch() {
                Some(ch) => {
                    if f(ch) { token.push_char(ch) }
                    else { break }
                }
                None => { break }
            }
            self.advance();
        }

        return token;
    }

    fn parse_section_identifier(&mut self) -> ~str {
        self.read_token(|ch| {
            match ch {
                'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_' => true,
                _ => false
            }
        })
    }

    fn skip_whitespaces(&mut self) {
        loop {
            match self.ch() {
                Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
                    self.advance();
                }
                _ => { break }
            }
        }
    }

    fn skip_whitespaces_and_comments(&mut self) {
        loop {
            match self.ch() {
                Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
                    self.advance();
                }
                Some('#') => {
                    self.skip_comment();
                }
                _ => { break }
            }
        }
    }

    fn skip_comment(&mut self) {
        assert!(self.ch() == Some('#'));
        // skip to end of line
        loop {
            self.advance();
            match self.ch() {
                Some('\n') => { break }
                None => { return }
                _ => { /* skip */ }
            }
        }
        self.advance();
    }

    pub fn parse<V: Visitor>(&mut self, visitor: &mut V) -> bool {
        loop {
            if self.eos() { return true }
            match self.ch().unwrap() {
                // ignore whitespace
                '\r' | '\n' | ' ' | '\t' => {
                    self.advance();
                }

                // comment
                '#' => {
                    self.skip_comment();
                }

                // section
                '[' => {
                    self.advance();
                    let mut double_section = false;
                    match self.ch() {
                        Some('[') => {
                            double_section = true;
                            self.advance();
                        }
                        _ => {}
                    }

                    let section_name = self.parse_section_identifier();

                    if !self.advance_if(']') { return false }
                    if double_section {
                        if !self.advance_if(']') { return false }
                    }

                    visitor.section(section_name, double_section);
                }

                // identifier
                'a' .. 'z' | 'A' .. 'Z' | '_' => {

                    let ident = self.read_token(|ch| {
                        match ch {
                            'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' => true,
                            _ => false
                        }
                    });

                    self.skip_whitespaces();

                    if !self.advance_if('=') { return false } // assign wanted

                    match self.parse_value() {
                        Some(val) => { visitor.pair(ident, val); }
                        None => { return false; }
                    }
                    // do not advance!
                }

                _ => { return false }
            } /* end match */
        }

        assert!(false);
    }
}
<|endoftext|>


<commit_before>use crate::cpp_code_generator;
use crate::cpp_code_generator::generate_cpp_type_size_requester;
use crate::processor::ProcessingStep;
use crate::processor::ProcessorData;
use crate::rust_code_generator;
use crate::versions;
use pathdiff::diff_paths;
use ritual_common::errors::{err_msg, Result};
use ritual_common::file_utils::copy_file;
use ritual_common::file_utils::copy_recursively;
use ritual_common::file_utils::create_dir;
use ritual_common::file_utils::create_dir_all;
use ritual_common::file_utils::create_file;
use ritual_common::file_utils::path_to_str;
use ritual_common::file_utils::read_dir;
use ritual_common::file_utils::remove_dir_all;
use ritual_common::file_utils::repo_crate_local_path;
use ritual_common::file_utils::save_json;
use ritual_common::file_utils::save_toml;
use ritual_common::toml;
use ritual_common::utils::run_command;
use ritual_common::utils::MapIfOk;
use ritual_common::BuildScriptData;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

/// Merges `a` and `b` recursively. `b` take precedence over `a`.
fn recursive_merge_toml(a: toml::Value, b: toml::Value) -> toml::Value {
    if a.same_type(&b) {
        if let toml::Value::Array(mut a_array) = a {
            if let toml::Value::Array(mut b_array) = b {
                a_array.append(&mut b_array);
                toml::Value::Array(a_array)
            } else {
                unreachable!()
            }
        } else if let toml::Value::Table(mut a_table) = a {
            if let toml::Value::Table(b_table) = b {
                for (key, value) in b_table {
                    if let Some(old_value) = a_table.remove(&key) {
                        a_table.insert(key, recursive_merge_toml(old_value, value));
                    } else {
                        a_table.insert(key, value);
                    }
                }
                toml::Value::Table(a_table)
            } else {
                unreachable!()
            }
        } else {
            b
        }
    } else {
        b
    }
}

/// Generates `Cargo.toml` file and skeleton of the crate.
/// If a crate template was supplied, files from it are
/// copied to the output location.
fn generate_crate_template(data: &mut ProcessorData) -> Result<()> {
    let output_path = data
        .workspace
        .crate_path(data.config.crate_properties().name())?;

    let template_build_rs_path =
        data.config
            .crate_template_path()
            .as_ref()
            .and_then(|crate_template_path| {
                let template_build_rs_path = crate_template_path.join("build.rs");
                if template_build_rs_path.exists() {
                    Some(template_build_rs_path)
                } else {
                    None
                }
            });
    let output_build_rs_path = output_path.join("build.rs");
    if let Some(ref template_build_rs_path) = template_build_rs_path {
        copy_file(template_build_rs_path, output_build_rs_path)?;
    } else {
        let mut build_rs_file = create_file(&output_build_rs_path)?;
        write!(
            build_rs_file,
            "{}",
            include_str!("../templates/crate/build.rs")
        )?;
    }
    let cargo_toml_data = {
        let package = toml::value::Value::Table({
            let mut table = toml::value::Table::new();
            table.insert(
                "name".to_string(),
                toml::Value::String(data.config.crate_properties().name().into()),
            );
            table.insert(
                "version".to_string(),
                toml::Value::String(data.config.crate_properties().version().into()),
            );
            table.insert(
                "build".to_string(),
                toml::Value::String("build.rs".to_string()),
            );
            table.insert(
                "edition".to_string(),
                toml::Value::String("2018".to_string()),
            );
            table
        });
        let dep_value = |version: &str, local_path: Option<PathBuf>| -> Result<toml::Value> {
            Ok(
                if local_path.is_none() || !data.workspace.config().write_dependencies_local_paths {
                    toml::Value::String(version.to_string())
                } else {
                    toml::Value::Table({
                        let mut value = toml::value::Table::new();
                        value.insert(
                            "version".to_string(),
                            toml::Value::String(version.to_string()),
                        );
                        value.insert(
                            "path".to_string(),
                            toml::Value::String(
                                path_to_str(&local_path.expect("checked above"))?.to_string(),
                            ),
                        );
                        value
                    })
                },
            )
        };
        let dependencies = toml::Value::Table({
            let mut table = toml::value::Table::new();
            if !data
                .config
                .crate_properties()
                .should_remove_default_dependencies()
            {
                table.insert(
                    "cpp_utils".to_string(),
                    dep_value(
                        versions::CPP_UTILS_VERSION,
                        if data.workspace.config().write_dependencies_local_paths {
                            Some(repo_crate_local_path("cpp_utils")?)
                        } else {
                            None
                        },
                    )?,
                );
                for dep in data.dep_databases {
                    let relative_path =
                        diff_paths(&data.workspace.crate_path(&dep.crate_name)?, &output_path)
                            .ok_or_else(|| {
                                err_msg("failed to get relative path to the dependency")
                            })?;

                    table.insert(
                        dep.crate_name.clone(),
                        dep_value(&dep.crate_version, Some(relative_path))?,
                    );
                }
            }
            for dep in data.config.crate_properties().dependencies() {
                table.insert(
                    dep.name().to_string(),
                    dep_value(dep.version(), dep.local_path().cloned())?,
                );
            }
            table
        });
        let build_dependencies = toml::Value::Table({
            let mut table = toml::value::Table::new();
            if !data
                .config
                .crate_properties()
                .should_remove_default_build_dependencies()
            {
                table.insert(
                    "ritual_build".to_string(),
                    dep_value(
                        versions::RITUAL_BUILD_VERSION,
                        if data.workspace.config().write_dependencies_local_paths {
                            Some(repo_crate_local_path("ritual_build")?)
                        } else {
                            None
                        },
                    )?,
                );
            }
            for dep in data.config.crate_properties().build_dependencies() {
                table.insert(
                    dep.name().to_string(),
                    dep_value(dep.version(), dep.local_path().cloned())?,
                );
            }
            table
        });
        let mut table = toml::value::Table::new();
        table.insert("package".to_string(), package);
        table.insert("dependencies".to_string(), dependencies);
        table.insert("build-dependencies".to_string(), build_dependencies);
        recursive_merge_toml(
            toml::Value::Table(table),
            toml::Value::Table(data.config.crate_properties().custom_fields().clone()),
        )
    };
    save_toml(output_path.join("Cargo.toml"), &cargo_toml_data)?;

    if let Some(ref template_path) = data.config.crate_template_path() {
        for item in read_dir(template_path)? {
            let item = item?;
            let target = output_path.join(item.file_name());
            if target.exists() {
                remove_dir_all(&target)?;
            }
            copy_recursively(&item.path(), &target)?;
        }
    }
    if !output_path.join("src").exists() {
        create_dir_all(output_path.join("src"))?;
    }
    Ok(())
}

/// Generates main files and directories of the library.
fn generate_c_lib_template(
    lib_name: &str,
    lib_path: &Path,
    global_header_name: &str,
    include_directives: &[PathBuf],
) -> Result<()> {
    let name_upper = lib_name.to_uppercase();
    let cmakelists_path = lib_path.join("CMakeLists.txt");
    let mut cmakelists_file = create_file(&cmakelists_path)?;

    write!(
        cmakelists_file,
        include_str!("../templates/c_lib/CMakeLists.txt"),
        lib_name_lowercase = lib_name,
        lib_name_uppercase = name_upper
    )?;

    let include_directives_code = include_directives
        .map_if_ok(|d| -> Result<_> { Ok(format!("#include \"{}\"", path_to_str(d)?)) })?
        .join("\n");

    let global_header_path = lib_path.join(&global_header_name);
    let mut global_header_file = create_file(&global_header_path)?;
    write!(
        global_header_file,
        include_str!("../templates/c_lib/global.h"),
        include_directives_code = include_directives_code
    )?;
    Ok(())
}

fn run(data: &mut ProcessorData) -> Result<()> {
    generate_crate_template(data)?;

    let output_path = data
        .workspace
        .crate_path(data.config.crate_properties().name())?;

    let c_lib_path = output_path.join("c_lib");
    if !c_lib_path.exists() {
        create_dir(&c_lib_path)?;
    }
    let c_lib_name = format!("{}_c", data.config.crate_properties().name());
    let global_header_name = format!("{}_global.h", c_lib_name);
    generate_c_lib_template(
        &c_lib_name,
        &c_lib_path,
        &global_header_name,
        data.config.include_directives(),
    )?;

    cpp_code_generator::generate_cpp_file(
        &data.current_database.cpp_items,
        &c_lib_path.join("file1.cpp"),
        &global_header_name,
    )?;

    let file = create_file(c_lib_path.join("sized_types.cxx"))?;
    generate_cpp_type_size_requester(
        &data.current_database.rust_database,
        data.config.include_directives(),
        file,
    )?;

    rust_code_generator::generate(
        data.config.crate_properties().name(),
        &data.current_database.rust_database,
        &output_path.join("src"),
    )?;

    run_command(Command::new("cargo").arg("fmt").current_dir(&output_path))?;
    run_command(
        Command::new("rustfmt")
            .arg("src/ffi.in.rs")
            .current_dir(&output_path),
    )?;

    save_json(
        output_path.join("build_script_data.json"),
        &BuildScriptData {
            cpp_build_config: data.config.cpp_build_config().clone(),
            cpp_wrapper_lib_name: c_lib_name,
            cpp_lib_version: data.config.cpp_lib_version().map(|s| s.to_string()),
        },
    )?;
    Ok(())
}

pub fn crate_writer_step() -> ProcessingStep {
    // TODO: set dependencies
    ProcessingStep::new_const("crate_writer", run)
}
<commit_msg>cleanup old source when generating crate<commit_after>use crate::cpp_code_generator;
use crate::cpp_code_generator::generate_cpp_type_size_requester;
use crate::processor::ProcessingStep;
use crate::processor::ProcessorData;
use crate::rust_code_generator;
use crate::versions;
use pathdiff::diff_paths;
use ritual_common::errors::{err_msg, Result};
use ritual_common::file_utils::copy_file;
use ritual_common::file_utils::copy_recursively;
use ritual_common::file_utils::create_dir;
use ritual_common::file_utils::create_dir_all;
use ritual_common::file_utils::create_file;
use ritual_common::file_utils::path_to_str;
use ritual_common::file_utils::read_dir;
use ritual_common::file_utils::remove_dir_all;
use ritual_common::file_utils::repo_crate_local_path;
use ritual_common::file_utils::save_json;
use ritual_common::file_utils::save_toml;
use ritual_common::toml;
use ritual_common::utils::run_command;
use ritual_common::utils::MapIfOk;
use ritual_common::BuildScriptData;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

/// Merges `a` and `b` recursively. `b` take precedence over `a`.
fn recursive_merge_toml(a: toml::Value, b: toml::Value) -> toml::Value {
    if a.same_type(&b) {
        if let toml::Value::Array(mut a_array) = a {
            if let toml::Value::Array(mut b_array) = b {
                a_array.append(&mut b_array);
                toml::Value::Array(a_array)
            } else {
                unreachable!()
            }
        } else if let toml::Value::Table(mut a_table) = a {
            if let toml::Value::Table(b_table) = b {
                for (key, value) in b_table {
                    if let Some(old_value) = a_table.remove(&key) {
                        a_table.insert(key, recursive_merge_toml(old_value, value));
                    } else {
                        a_table.insert(key, value);
                    }
                }
                toml::Value::Table(a_table)
            } else {
                unreachable!()
            }
        } else {
            b
        }
    } else {
        b
    }
}

/// Generates `Cargo.toml` file and skeleton of the crate.
/// If a crate template was supplied, files from it are
/// copied to the output location.
fn generate_crate_template(data: &mut ProcessorData) -> Result<()> {
    let output_path = data
        .workspace
        .crate_path(data.config.crate_properties().name())?;

    let template_build_rs_path =
        data.config
            .crate_template_path()
            .as_ref()
            .and_then(|crate_template_path| {
                let template_build_rs_path = crate_template_path.join("build.rs");
                if template_build_rs_path.exists() {
                    Some(template_build_rs_path)
                } else {
                    None
                }
            });
    let output_build_rs_path = output_path.join("build.rs");
    if let Some(ref template_build_rs_path) = template_build_rs_path {
        copy_file(template_build_rs_path, output_build_rs_path)?;
    } else {
        let mut build_rs_file = create_file(&output_build_rs_path)?;
        write!(
            build_rs_file,
            "{}",
            include_str!("../templates/crate/build.rs")
        )?;
    }
    let cargo_toml_data = {
        let package = toml::value::Value::Table({
            let mut table = toml::value::Table::new();
            table.insert(
                "name".to_string(),
                toml::Value::String(data.config.crate_properties().name().into()),
            );
            table.insert(
                "version".to_string(),
                toml::Value::String(data.config.crate_properties().version().into()),
            );
            table.insert(
                "build".to_string(),
                toml::Value::String("build.rs".to_string()),
            );
            table.insert(
                "edition".to_string(),
                toml::Value::String("2018".to_string()),
            );
            table
        });
        let dep_value = |version: &str, local_path: Option<PathBuf>| -> Result<toml::Value> {
            Ok(
                if local_path.is_none() || !data.workspace.config().write_dependencies_local_paths {
                    toml::Value::String(version.to_string())
                } else {
                    toml::Value::Table({
                        let mut value = toml::value::Table::new();
                        value.insert(
                            "version".to_string(),
                            toml::Value::String(version.to_string()),
                        );
                        value.insert(
                            "path".to_string(),
                            toml::Value::String(
                                path_to_str(&local_path.expect("checked above"))?.to_string(),
                            ),
                        );
                        value
                    })
                },
            )
        };
        let dependencies = toml::Value::Table({
            let mut table = toml::value::Table::new();
            if !data
                .config
                .crate_properties()
                .should_remove_default_dependencies()
            {
                table.insert(
                    "cpp_utils".to_string(),
                    dep_value(
                        versions::CPP_UTILS_VERSION,
                        if data.workspace.config().write_dependencies_local_paths {
                            Some(repo_crate_local_path("cpp_utils")?)
                        } else {
                            None
                        },
                    )?,
                );
                for dep in data.dep_databases {
                    let relative_path =
                        diff_paths(&data.workspace.crate_path(&dep.crate_name)?, &output_path)
                            .ok_or_else(|| {
                                err_msg("failed to get relative path to the dependency")
                            })?;

                    table.insert(
                        dep.crate_name.clone(),
                        dep_value(&dep.crate_version, Some(relative_path))?,
                    );
                }
            }
            for dep in data.config.crate_properties().dependencies() {
                table.insert(
                    dep.name().to_string(),
                    dep_value(dep.version(), dep.local_path().cloned())?,
                );
            }
            table
        });
        let build_dependencies = toml::Value::Table({
            let mut table = toml::value::Table::new();
            if !data
                .config
                .crate_properties()
                .should_remove_default_build_dependencies()
            {
                table.insert(
                    "ritual_build".to_string(),
                    dep_value(
                        versions::RITUAL_BUILD_VERSION,
                        if data.workspace.config().write_dependencies_local_paths {
                            Some(repo_crate_local_path("ritual_build")?)
                        } else {
                            None
                        },
                    )?,
                );
            }
            for dep in data.config.crate_properties().build_dependencies() {
                table.insert(
                    dep.name().to_string(),
                    dep_value(dep.version(), dep.local_path().cloned())?,
                );
            }
            table
        });
        let mut table = toml::value::Table::new();
        table.insert("package".to_string(), package);
        table.insert("dependencies".to_string(), dependencies);
        table.insert("build-dependencies".to_string(), build_dependencies);
        recursive_merge_toml(
            toml::Value::Table(table),
            toml::Value::Table(data.config.crate_properties().custom_fields().clone()),
        )
    };
    save_toml(output_path.join("Cargo.toml"), &cargo_toml_data)?;

    if let Some(ref template_path) = data.config.crate_template_path() {
        for item in read_dir(template_path)? {
            let item = item?;
            let target = output_path.join(item.file_name());
            if target.exists() {
                remove_dir_all(&target)?;
            }
            copy_recursively(&item.path(), &target)?;
        }
    }
    if !output_path.join("src").exists() {
        create_dir_all(output_path.join("src"))?;
    }
    Ok(())
}

/// Generates main files and directories of the library.
fn generate_c_lib_template(
    lib_name: &str,
    lib_path: &Path,
    global_header_name: &str,
    include_directives: &[PathBuf],
) -> Result<()> {
    let name_upper = lib_name.to_uppercase();
    let cmakelists_path = lib_path.join("CMakeLists.txt");
    let mut cmakelists_file = create_file(&cmakelists_path)?;

    write!(
        cmakelists_file,
        include_str!("../templates/c_lib/CMakeLists.txt"),
        lib_name_lowercase = lib_name,
        lib_name_uppercase = name_upper
    )?;

    let include_directives_code = include_directives
        .map_if_ok(|d| -> Result<_> { Ok(format!("#include \"{}\"", path_to_str(d)?)) })?
        .join("\n");

    let global_header_path = lib_path.join(&global_header_name);
    let mut global_header_file = create_file(&global_header_path)?;
    write!(
        global_header_file,
        include_str!("../templates/c_lib/global.h"),
        include_directives_code = include_directives_code
    )?;
    Ok(())
}

fn run(data: &mut ProcessorData) -> Result<()> {
    generate_crate_template(data)?;

    let output_path = data
        .workspace
        .crate_path(data.config.crate_properties().name())?;

    let c_lib_path = output_path.join("c_lib");
    if !c_lib_path.exists() {
        create_dir(&c_lib_path)?;
    }
    let c_lib_name = format!("{}_c", data.config.crate_properties().name());
    let global_header_name = format!("{}_global.h", c_lib_name);
    generate_c_lib_template(
        &c_lib_name,
        &c_lib_path,
        &global_header_name,
        data.config.include_directives(),
    )?;

    cpp_code_generator::generate_cpp_file(
        &data.current_database.cpp_items,
        &c_lib_path.join("file1.cpp"),
        &global_header_name,
    )?;

    let file = create_file(c_lib_path.join("sized_types.cxx"))?;
    generate_cpp_type_size_requester(
        &data.current_database.rust_database,
        data.config.include_directives(),
        file,
    )?;

    let rust_src_path = output_path.join("src");
    if rust_src_path.exists() {
        remove_dir_all(&rust_src_path)?;
    }
    create_dir_all(&rust_src_path)?;
    rust_code_generator::generate(
        data.config.crate_properties().name(),
        &data.current_database.rust_database,
        &rust_src_path,
    )?;

    run_command(Command::new("cargo").arg("fmt").current_dir(&output_path))?;
    run_command(
        Command::new("rustfmt")
            .arg("src/ffi.in.rs")
            .current_dir(&output_path),
    )?;

    save_json(
        output_path.join("build_script_data.json"),
        &BuildScriptData {
            cpp_build_config: data.config.cpp_build_config().clone(),
            cpp_wrapper_lib_name: c_lib_name,
            cpp_lib_version: data.config.cpp_lib_version().map(|s| s.to_string()),
        },
    )?;
    Ok(())
}

pub fn crate_writer_step() -> ProcessingStep {
    // TODO: set dependencies
    ProcessingStep::new_const("crate_writer", run)
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the file stat action.
//!
//! A file stat action responses with stat of a given file

use std::fs::Metadata;
use std::path::PathBuf;

use log::warn;

use crate::session::{self, Error, Session};

impl From<std::io::Error> for Error {

    fn from(e: std::io::Error) -> Error {
        Error::action(e)
    }
}

#[derive(Debug)]
pub struct Response {
    metadata: Metadata,
    #[cfg(target_os = "linux")]
    flags_linux: Option<u32>,
    symlink: Option<PathBuf>,
    path: PathBuf,
    #[cfg(target_family = "unix")]
    ext_attrs: Vec<crate::fs::unix::ExtAttr>,
}

#[derive(Debug)]
pub struct Request {
    path: PathBuf,
    collect_ext_attrs: bool,
    follow_symlink: bool,
}

pub fn handle<S: Session>(session: &mut S, request: Request) -> session::Result<()> {
    let metadata = if request.follow_symlink {
        std::fs::metadata(&request.path)?
    } else {
        std::fs::symlink_metadata(&request.path)?
    };

    let symlink = if metadata.file_type().is_symlink() {
        std::fs::read_link(&request.path).map_err(|error| {
            // TODO: Make the `ack!` macro more expressive and rewrite it.
            warn! {
                "failed to read symlink for '{path}': {cause}",
                path = request.path.display(),
                cause = error,
            }
        }).ok()
    } else {
        None
    };

    #[cfg(target_os = "linux")]
    let flags_linux = crate::fs::linux::flags(&request.path).map_err(|error| {
        // TODO: Make the `ack!` macro more expressive and rewrite it.
        warn! {
            "failed to collect flags for '{path}': {cause}",
            path = request.path.display(),
            cause = error,
        }
    }).ok();

    let mut response = Response {
        path: request.path,
        metadata: metadata,
        symlink: symlink,
        #[cfg(target_family = "unix")]
        ext_attrs: vec!(),
        #[cfg(target_os = "linux")]
        flags_linux: flags_linux,
    };

    if request.collect_ext_attrs {
        // TODO: This is not pretty. Consider creating a blank `ext_attrs`
        // implementation for Windows and make this code compile regardless of
        // the platform.
        #[cfg(target_family = "unix")]
        {
            // TODO: Do not fail on error.
            response.ext_attrs.extend(crate::fs::unix::ext_attrs(&response.path)?);
        }
    }

    session.reply(response)?;
    Ok(())
}

impl super::Request for Request {

    type Proto = rrg_proto::GetFileStatRequest;

    fn from_proto(proto: Self::Proto) -> Result<Self, session::ParseError> {
        use std::convert::TryInto as _;

        let path = proto.pathspec
            .ok_or(session::MissingFieldError::new("path spec"))?
            .try_into().map_err(session::ParseError::malformed)?;

        Ok(Request {
            path: path,
            follow_symlink: proto.follow_symlink.unwrap_or(false),
            collect_ext_attrs: proto.collect_ext_attrs.unwrap_or(false),
        })
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("StatEntry");

    type Proto = rrg_proto::StatEntry;

    fn into_proto(self) -> Self::Proto {
        use rrg_proto::convert::IntoLossy as _;

        rrg_proto::StatEntry {
            pathspec: Some(self.path.into()),
            #[cfg(target_os = "linux")]
            st_flags_linux: self.flags_linux,
            #[cfg(target_family = "unix")]
            ext_attrs: self.ext_attrs.into_iter().map(Into::into).collect(),
            ..self.metadata.into_lossy()
        }
    }
}

#[cfg(test)]
mod tests {
}
<commit_msg>Reorder fields of the stat action structs.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

//! A handler and associated types for the file stat action.
//!
//! A file stat action responses with stat of a given file

use std::fs::Metadata;
use std::path::PathBuf;

use log::warn;

use crate::session::{self, Error, Session};

impl From<std::io::Error> for Error {

    fn from(e: std::io::Error) -> Error {
        Error::action(e)
    }
}

#[derive(Debug)]
pub struct Request {
    path: PathBuf,
    collect_ext_attrs: bool,
    follow_symlink: bool,
}

#[derive(Debug)]
pub struct Response {
    path: PathBuf,
    metadata: Metadata,
    symlink: Option<PathBuf>,
    #[cfg(target_family = "unix")]
    ext_attrs: Vec<crate::fs::unix::ExtAttr>,
    #[cfg(target_os = "linux")]
    flags_linux: Option<u32>,
}

pub fn handle<S: Session>(session: &mut S, request: Request) -> session::Result<()> {
    let metadata = if request.follow_symlink {
        std::fs::metadata(&request.path)?
    } else {
        std::fs::symlink_metadata(&request.path)?
    };

    let symlink = if metadata.file_type().is_symlink() {
        std::fs::read_link(&request.path).map_err(|error| {
            // TODO: Make the `ack!` macro more expressive and rewrite it.
            warn! {
                "failed to read symlink for '{path}': {cause}",
                path = request.path.display(),
                cause = error,
            }
        }).ok()
    } else {
        None
    };

    #[cfg(target_os = "linux")]
    let flags_linux = crate::fs::linux::flags(&request.path).map_err(|error| {
        // TODO: Make the `ack!` macro more expressive and rewrite it.
        warn! {
            "failed to collect flags for '{path}': {cause}",
            path = request.path.display(),
            cause = error,
        }
    }).ok();

    let mut response = Response {
        path: request.path,
        metadata: metadata,
        symlink: symlink,
        #[cfg(target_family = "unix")]
        ext_attrs: vec!(),
        #[cfg(target_os = "linux")]
        flags_linux: flags_linux,
    };

    if request.collect_ext_attrs {
        // TODO: This is not pretty. Consider creating a blank `ext_attrs`
        // implementation for Windows and make this code compile regardless of
        // the platform.
        #[cfg(target_family = "unix")]
        {
            // TODO: Do not fail on error.
            response.ext_attrs.extend(crate::fs::unix::ext_attrs(&response.path)?);
        }
    }

    session.reply(response)?;
    Ok(())
}

impl super::Request for Request {

    type Proto = rrg_proto::GetFileStatRequest;

    fn from_proto(proto: Self::Proto) -> Result<Self, session::ParseError> {
        use std::convert::TryInto as _;

        let path = proto.pathspec
            .ok_or(session::MissingFieldError::new("path spec"))?
            .try_into().map_err(session::ParseError::malformed)?;

        Ok(Request {
            path: path,
            follow_symlink: proto.follow_symlink.unwrap_or(false),
            collect_ext_attrs: proto.collect_ext_attrs.unwrap_or(false),
        })
    }
}

impl super::Response for Response {

    const RDF_NAME: Option<&'static str> = Some("StatEntry");

    type Proto = rrg_proto::StatEntry;

    fn into_proto(self) -> Self::Proto {
        use rrg_proto::convert::IntoLossy as _;

        rrg_proto::StatEntry {
            pathspec: Some(self.path.into()),
            #[cfg(target_family = "unix")]
            ext_attrs: self.ext_attrs.into_iter().map(Into::into).collect(),
            #[cfg(target_os = "linux")]
            st_flags_linux: self.flags_linux,
            ..self.metadata.into_lossy()
        }
    }
}

#[cfg(test)]
mod tests {
}
<|endoftext|>


<commit_before>use bar::Bar;
use std::rc::Rc;
use error::{Result, ErrorKind};
use item_state::ItemState;
use bar_properties::BarProperties;
use futures::Stream;
use pango::FontDescription;
use xcb_event_stream::XcbEventStream;
use tokio_core::reactor::{Core, Handle};
use component::{Slot, ComponentUpdate, ComponentCreator};
use xcb::{self, Visualtype, Screen, Window, Rectangle, Connection, randr};

#[derive(Clone)]
/// Defines a color by it's red, green and blue components.
pub struct Color {
    pub red: f64,
    pub green: f64,
    pub blue: f64,
}

impl Color {
    pub fn new(red: f64, green: f64, blue: f64) -> Color {
        Color { red, green, blue }
    }

    /// Transforms this struct into a u32, which is in reality C-packed struct
    /// with the red, green, blue and alpha fields.
    /// Colors represented in this form are required by XCB.
    pub fn as_u32(&self) -> u32 {
        (((255. * self.red).round() as u32) << 16) + (((255. * self.green).round() as u32) << 8) +
            (((255. * self.blue).round() as u32) << 0)
    }
}

/// Bar position relative to the screen.
#[derive(Clone)]
pub enum Position {
    Top,
    Bottom,
}

/// Bar geometry.
#[derive(Clone)]
pub enum Geometry {
    /// Define bar's geometry with absolute coordinates.
    Absolute(Rectangle),
    /// Define bar's geometry relative to the screen.
    Relative {
        /// The bar's position relative to the screen.
        position: Position,
        /// The bar's height in pixels.
        height: u16,
        /// Space between the bar and the top or bottom side of the screen in pixels.
        padding_x: u16,
        /// Space between the bar and the left and right side of the screen in pixels.
        padding_y: u16,
    },
}

impl Default for Geometry {
    fn default() -> Geometry {
        Geometry::Relative {
            position: Position::Top,
            height: 20,
            padding_y: 0,
            padding_x: 0,
        }
    }
}

pub type UpdateStream = Box<Stream<Item = ComponentUpdate, Error = ::error::Error>>;

/// Struct implementing the builder pattern for `Bar`.
pub struct BarBuilder {
    geometry: Geometry,
    bg_color: Color,
    fg_color: Color,
    font_name: String,
    items: Vec<(Slot, Box<ComponentCreator>)>,
}

impl BarBuilder {
    /// Create a new `BarBuilder` with default properties.
    pub fn new() -> BarBuilder {
        BarBuilder {
            geometry: Default::default(),
            bg_color: Color::new(1., 1., 1.),
            fg_color: Color::new(0., 0., 0.),
            items: vec![],
            font_name: String::new(),
        }
    }

    /// Create a `tokio` event loop and run launch the bar on it.
    pub fn run(self) -> Result<()> {
        let mut core = Core::new()?;
        let bar = self.build(core.handle())?;
        let future = bar.run();
        core.run(future).map_err(|()| "event loop error".into())
    }

    /// Adds a component to the bar into the specified slot.
    pub fn add_component<C>(mut self, slot: Slot, component: C) -> Self
    where
        C: ComponentCreator + 'static,
    {
        self.items.push((slot, Box::new(component)));
        self
    }

    /// Set the bar's position and size.
    pub fn geometry(mut self, geometry: Geometry) -> Self {
        self.geometry = geometry;
        self
    }

    /// Set the default font.
    pub fn font<S: AsRef<str>>(mut self, font: S) -> Self {
        self.font_name = font.as_ref().to_string();
        self
    }

    /// Set the default foreground color. (The text color)
    pub fn foreground(mut self, color: Color) -> Self {
        self.fg_color = color;
        self
    }

    /// Set the default background color.
    pub fn background(mut self, color: Color) -> Self {
        self.bg_color = color;
        self
    }

    /// Consumes and splits self into `self.items` and `BarProperties` struct,
    /// containing everything else relevant.
    fn into_items_and_props(
        self,
        area: &Rectangle,
    ) -> (Vec<(Slot, Box<ComponentCreator>)>, BarProperties) {
        let props = BarProperties {
            geometry: self.geometry,
            area: area.clone(),
            accent_color: None,
            fg_color: self.fg_color,
            bg_color: self.bg_color,
            font: FontDescription::from_string(&*self.font_name),
        };
        (self.items, props)
    }

    /// Builds and returns the bar.
    pub fn build(self, handle: Handle) -> Result<Bar> {
        let (window_conn, _) = Connection::connect(None)
            .map_err(|e| ErrorKind::XcbConnection(e))?;
        let (conn, _) = Connection::connect(None)
            .map_err(|e| ErrorKind::XcbConnection(e))?;

        let geometry;
        let window;
        let visualtype;
        let foreground = conn.generate_id();

        // Scope for `screen`
        {
            let setup = conn.get_setup();
            let screen = setup.roots().next().unwrap();

            geometry = calculate_geometry(&screen, &self.geometry);
            window = create_window(&window_conn, &screen, &geometry, self.bg_color.as_u32())?;
            visualtype = find_visualtype(&screen).unwrap();

            // Create xcb graphics context for drawin te background
            try_xcb!(
                xcb::create_gc_checked,
                "failed to create gcontext",
                &conn,
                foreground,
                screen.root(),
                &[
                    (xcb::GC_FOREGROUND, self.bg_color.as_u32()),
                    (xcb::GC_GRAPHICS_EXPOSURES, 0)
                ]
            );
        }

        let conn = Rc::new(conn);

        let item_count = self.items.len();
        let mut left_items = vec![];
        let mut center_items = vec![];
        let mut right_items = vec![];
        let mut updates: Option<UpdateStream> = None;

        // Consumes self
        let (items, properties) = self.into_items_and_props(&geometry);
        let properties = Rc::new(properties);

        // Initiate components and convert them into a stream of
        // updates.  The sream also carries information about the
        // source component such as the id, slot and the index of
        // the component in the said slot.
        for (id, (slot, creator)) in items.into_iter().enumerate() {
            let vec = match slot {
                Slot::Left => &mut left_items,
                Slot::Center => &mut center_items,
                Slot::Right => &mut right_items,
            };
            let index = vec.len();

            vec.push(ItemState::new(
                id,
                properties.clone(),
                0,
                visualtype,
                conn.clone(),
                window,
            ));

            let stream = creator.create(handle.clone())?.map(move |value| {
                ComponentUpdate {
                    slot: slot,
                    index,
                    id,
                    value,
                }
            });
            updates = match updates {
                None => Some(Box::new(stream)),
                Some(other) => Some(Box::new(other.select(stream))),
            }
        }

        // Join the component update stream with
        // a stream carrying events from XCB.
        let stream = updates
            .unwrap_or_else(|| Box::new(::futures::stream::empty()))
            .merge(XcbEventStream::new(window_conn));

        Ok(Bar {
            center_items,
            conn,
            foreground,
            geometry,
            item_positions: vec![(0, 0); item_count],
            left_items,
            right_items,
            stream: Some(stream),
            window,
        })
    }
}

/// Finds a visual type matching the one of the screen provided.
fn find_visualtype<'s>(screen: &Screen<'s>) -> Option<Visualtype> {
    'DEPTH: for depth in screen.allowed_depths() {
        for visual in depth.visuals() {
            if visual.visual_id() == screen.root_visual() {
                return Some(visual);
            }
        }
    }
    None
}

/// Calculates the position and size of the bar on
/// screen given the Geometry struct and screen dimensions.
fn calculate_geometry<'s>(screen: &Screen<'s>, geometry: &Geometry) -> Rectangle {
    let screen_w = screen.width_in_pixels();
    let screen_h = screen.height_in_pixels();

    match geometry {
        &Geometry::Absolute(ref rect) => rect.clone(),
        &Geometry::Relative {
            ref position,
            height: bar_height,
            padding_x,
            padding_y,
        } => {
            let x;
            let y;
            let width;
            let height;

            match *position {
                Position::Top => {
                    x = padding_x as i16;
                    y = padding_y as i16;
                    width = screen_w - 2 * padding_x;
                    height = bar_height;
                }
                Position::Bottom => {
                    x = padding_x as i16;
                    y = (screen_h - bar_height - padding_y) as i16;
                    width = screen_w - 2 * padding_x;
                    height = bar_height;
                }
            };

            Rectangle::new(x, y, width, height)
        }
    }
}

// Get dimension of specified output
fn get_screen_dimensions<'s>(
    conn: &xcb::Connection,
    setup: &xcb::StructPtr<'s, xcb::ffi::xcb_setup_t>,
    query_output_name: &str,
) -> Result<xcb::Reply<xcb::ffi::randr::xcb_randr_get_crtc_info_reply_t>> {
    unsafe {
        // Get the root window
        let root_iterator = xcb::ffi::xproto::xcb_setup_roots_iterator(setup.ptr);
        let root_window = (*root_iterator.data).root;

        // Load screen resources of the root window
        // Return result on error
        let res_cookie = randr::get_screen_resources(conn, root_window);
        let res_reply = res_cookie
            .get_reply()
            .map_err(|_| "Unable to get screen resources")?;

        // Get all crtcs from the reply
        let crtcs = res_reply.crtcs();

        for crtc in crtcs {
            // Get info about crtc
            let crtc_info_cookie = randr::get_crtc_info(conn, *crtc, 0);
            let crtc_info_reply = crtc_info_cookie.get_reply();

            if let Ok(reply) = crtc_info_reply {
                // Skip this crtc if it has no width or output
                if reply.width() == 0 || reply.num_outputs() == 0 {
                    continue;
                }

                // Get info of crtc's first output for output name
                let output = reply.outputs()[0];
                let output_info_cookie = randr::get_output_info(conn, output, 0);
                let output_info_reply = output_info_cookie.get_reply();

                // Get the name of the first output
                let mut output_name = String::new();
                if let Ok(output_info_reply) = output_info_reply {
                    output_name = String::from_utf8_lossy(output_info_reply.name()).into();
                }

                // If the output name is the requested name, return the dimensions
                if output_name == query_output_name {
                    return Ok(reply);
                }
            }
        }
    }
    let error_msg = ["Unable to find output ", query_output_name].concat();
    Err(error_msg.into())
}

/// Convinience macro for setting EWHM properites.
macro_rules! set_ewhm_prop {
    ($conn:expr, $window:expr, $name:expr, @atom $value:expr) => {
        {
            let value_atom = xcb::intern_atom($conn, true, $value)
                .get_reply().unwrap().atom();
            set_ewhm_prop!($conn, $window, $name, &[value_atom]);
        }
    };
    ($conn:expr, $window:expr, $name:expr, $data:expr) => {
        {
            let type_atom = xcb::intern_atom($conn, true, $name)
                .get_reply().unwrap().atom();
            xcb::change_property(
                $conn,
                xcb::PROP_MODE_REPLACE as u8,
                $window,
                type_atom,
                xcb::ATOM_ATOM,
                32,
                $data);
        }
    };
}

/// Creates a Xorg window using XCB.
fn create_window<'s>(
    conn: &Connection,
    screen: &Screen<'s>,
    geometry: &Rectangle,
    background: u32,
) -> Result<Window> {
    let window = conn.generate_id();

    xcb::create_window(
        &conn,
        xcb::WINDOW_CLASS_COPY_FROM_PARENT as u8,
        window,
        screen.root(),
        geometry.x(),
        geometry.y(),
        geometry.width(),
        geometry.height(),
        0,
        xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
        screen.root_visual(),
        &[
            (xcb::CW_BACK_PIXEL, background), // Default background color
            (
                xcb::CW_EVENT_MASK, // What kinds of events are we
                xcb::EVENT_MASK_EXPOSURE |       //   interested in
             xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_ENTER_WINDOW,
            ),
            (xcb::CW_OVERRIDE_REDIRECT, 0),
        ],
    );

    // TODO: Struts tell the WM to reserve space for our bar. Derive these from the Geometry struct.
    let mut strut = vec![0; 12];
    strut[2] = 30;
    strut[8] = 5;
    strut[9] = 1915;

    set_ewhm_prop!(&conn, window, "_NET_WM_WINDOW_TYPE", @atom "_NET_WM_WINDOW_TYPE_DOCK");
    set_ewhm_prop!(&conn, window, "_NET_WM_STATE", @atom "_NET_WM_STATE_STICKY");
    set_ewhm_prop!(&conn, window, "_NET_WM_DESKTOP", &[-1]);
    set_ewhm_prop!(&conn, window, "_NET_WM_STRUT_PARTIAL", strut.as_slice());
    set_ewhm_prop!(&conn, window, "_NET_WM_STRUT", &strut[0..4]);
    set_ewhm_prop!(
        &conn,
        window,
        "_NET_WM_NAME",
        "xcbars".chars().collect::<Vec<_>>().as_slice()
    ); // TODO: Allow users to define custom window title for the bar.

    // Request the WM to manage our window.
    xcb::map_window(&conn, window);

    // Some WMs (such as OpenBox) require this.
    xcb::configure_window(
        &conn,
        window,
        &[
            (xcb::CONFIG_WINDOW_X as u16, geometry.x() as u32),
            (xcb::CONFIG_WINDOW_Y as u16, geometry.y() as u32),
        ],
    );

    conn.flush();

    Ok(window)
}
<commit_msg>Use safe interface to get root window<commit_after>use bar::Bar;
use std::rc::Rc;
use error::{Result, ErrorKind};
use item_state::ItemState;
use bar_properties::BarProperties;
use futures::Stream;
use pango::FontDescription;
use xcb_event_stream::XcbEventStream;
use tokio_core::reactor::{Core, Handle};
use component::{Slot, ComponentUpdate, ComponentCreator};
use xcb::{self, Visualtype, Screen, Window, Rectangle, Connection, randr};

#[derive(Clone)]
/// Defines a color by it's red, green and blue components.
pub struct Color {
    pub red: f64,
    pub green: f64,
    pub blue: f64,
}

impl Color {
    pub fn new(red: f64, green: f64, blue: f64) -> Color {
        Color { red, green, blue }
    }

    /// Transforms this struct into a u32, which is in reality C-packed struct
    /// with the red, green, blue and alpha fields.
    /// Colors represented in this form are required by XCB.
    pub fn as_u32(&self) -> u32 {
        (((255. * self.red).round() as u32) << 16) + (((255. * self.green).round() as u32) << 8) +
            (((255. * self.blue).round() as u32) << 0)
    }
}

/// Bar position relative to the screen.
#[derive(Clone)]
pub enum Position {
    Top,
    Bottom,
}

/// Bar geometry.
#[derive(Clone)]
pub enum Geometry {
    /// Define bar's geometry with absolute coordinates.
    Absolute(Rectangle),
    /// Define bar's geometry relative to the screen.
    Relative {
        /// The bar's position relative to the screen.
        position: Position,
        /// The bar's height in pixels.
        height: u16,
        /// Space between the bar and the top or bottom side of the screen in pixels.
        padding_x: u16,
        /// Space between the bar and the left and right side of the screen in pixels.
        padding_y: u16,
    },
}

impl Default for Geometry {
    fn default() -> Geometry {
        Geometry::Relative {
            position: Position::Top,
            height: 20,
            padding_y: 0,
            padding_x: 0,
        }
    }
}

pub type UpdateStream = Box<Stream<Item = ComponentUpdate, Error = ::error::Error>>;

/// Struct implementing the builder pattern for `Bar`.
pub struct BarBuilder {
    geometry: Geometry,
    bg_color: Color,
    fg_color: Color,
    font_name: String,
    items: Vec<(Slot, Box<ComponentCreator>)>,
}

impl BarBuilder {
    /// Create a new `BarBuilder` with default properties.
    pub fn new() -> BarBuilder {
        BarBuilder {
            geometry: Default::default(),
            bg_color: Color::new(1., 1., 1.),
            fg_color: Color::new(0., 0., 0.),
            items: vec![],
            font_name: String::new(),
        }
    }

    /// Create a `tokio` event loop and run launch the bar on it.
    pub fn run(self) -> Result<()> {
        let mut core = Core::new()?;
        let bar = self.build(core.handle())?;
        let future = bar.run();
        core.run(future).map_err(|()| "event loop error".into())
    }

    /// Adds a component to the bar into the specified slot.
    pub fn add_component<C>(mut self, slot: Slot, component: C) -> Self
    where
        C: ComponentCreator + 'static,
    {
        self.items.push((slot, Box::new(component)));
        self
    }

    /// Set the bar's position and size.
    pub fn geometry(mut self, geometry: Geometry) -> Self {
        self.geometry = geometry;
        self
    }

    /// Set the default font.
    pub fn font<S: AsRef<str>>(mut self, font: S) -> Self {
        self.font_name = font.as_ref().to_string();
        self
    }

    /// Set the default foreground color. (The text color)
    pub fn foreground(mut self, color: Color) -> Self {
        self.fg_color = color;
        self
    }

    /// Set the default background color.
    pub fn background(mut self, color: Color) -> Self {
        self.bg_color = color;
        self
    }

    /// Consumes and splits self into `self.items` and `BarProperties` struct,
    /// containing everything else relevant.
    fn into_items_and_props(
        self,
        area: &Rectangle,
    ) -> (Vec<(Slot, Box<ComponentCreator>)>, BarProperties) {
        let props = BarProperties {
            geometry: self.geometry,
            area: area.clone(),
            accent_color: None,
            fg_color: self.fg_color,
            bg_color: self.bg_color,
            font: FontDescription::from_string(&*self.font_name),
        };
        (self.items, props)
    }

    /// Builds and returns the bar.
    pub fn build(self, handle: Handle) -> Result<Bar> {
        let (window_conn, _) = Connection::connect(None)
            .map_err(|e| ErrorKind::XcbConnection(e))?;
        let (conn, _) = Connection::connect(None)
            .map_err(|e| ErrorKind::XcbConnection(e))?;

        let geometry;
        let window;
        let visualtype;
        let foreground = conn.generate_id();

        // Scope for `screen`
        {
            let setup = conn.get_setup();
            let screen = setup.roots().next().unwrap();

            geometry = calculate_geometry(&screen, &self.geometry);
            window = create_window(&window_conn, &screen, &geometry, self.bg_color.as_u32())?;
            visualtype = find_visualtype(&screen).unwrap();

            // Create xcb graphics context for drawin te background
            try_xcb!(
                xcb::create_gc_checked,
                "failed to create gcontext",
                &conn,
                foreground,
                screen.root(),
                &[
                    (xcb::GC_FOREGROUND, self.bg_color.as_u32()),
                    (xcb::GC_GRAPHICS_EXPOSURES, 0)
                ]
            );
        }

        let conn = Rc::new(conn);

        let item_count = self.items.len();
        let mut left_items = vec![];
        let mut center_items = vec![];
        let mut right_items = vec![];
        let mut updates: Option<UpdateStream> = None;

        // Consumes self
        let (items, properties) = self.into_items_and_props(&geometry);
        let properties = Rc::new(properties);

        // Initiate components and convert them into a stream of
        // updates.  The sream also carries information about the
        // source component such as the id, slot and the index of
        // the component in the said slot.
        for (id, (slot, creator)) in items.into_iter().enumerate() {
            let vec = match slot {
                Slot::Left => &mut left_items,
                Slot::Center => &mut center_items,
                Slot::Right => &mut right_items,
            };
            let index = vec.len();

            vec.push(ItemState::new(
                id,
                properties.clone(),
                0,
                visualtype,
                conn.clone(),
                window,
            ));

            let stream = creator.create(handle.clone())?.map(move |value| {
                ComponentUpdate {
                    slot: slot,
                    index,
                    id,
                    value,
                }
            });
            updates = match updates {
                None => Some(Box::new(stream)),
                Some(other) => Some(Box::new(other.select(stream))),
            }
        }

        // Join the component update stream with
        // a stream carrying events from XCB.
        let stream = updates
            .unwrap_or_else(|| Box::new(::futures::stream::empty()))
            .merge(XcbEventStream::new(window_conn));

        Ok(Bar {
            center_items,
            conn,
            foreground,
            geometry,
            item_positions: vec![(0, 0); item_count],
            left_items,
            right_items,
            stream: Some(stream),
            window,
        })
    }
}

/// Finds a visual type matching the one of the screen provided.
fn find_visualtype<'s>(screen: &Screen<'s>) -> Option<Visualtype> {
    'DEPTH: for depth in screen.allowed_depths() {
        for visual in depth.visuals() {
            if visual.visual_id() == screen.root_visual() {
                return Some(visual);
            }
        }
    }
    None
}

/// Calculates the position and size of the bar on
/// screen given the Geometry struct and screen dimensions.
fn calculate_geometry<'s>(screen: &Screen<'s>, geometry: &Geometry) -> Rectangle {
    let screen_w = screen.width_in_pixels();
    let screen_h = screen.height_in_pixels();

    match geometry {
        &Geometry::Absolute(ref rect) => rect.clone(),
        &Geometry::Relative {
            ref position,
            height: bar_height,
            padding_x,
            padding_y,
        } => {
            let x;
            let y;
            let width;
            let height;

            match *position {
                Position::Top => {
                    x = padding_x as i16;
                    y = padding_y as i16;
                    width = screen_w - 2 * padding_x;
                    height = bar_height;
                }
                Position::Bottom => {
                    x = padding_x as i16;
                    y = (screen_h - bar_height - padding_y) as i16;
                    width = screen_w - 2 * padding_x;
                    height = bar_height;
                }
            };

            Rectangle::new(x, y, width, height)
        }
    }
}

// Get dimension of specified output
fn get_screen_dimensions<'s>(
    screen: &Screen<'s>,
    conn: &xcb::Connection,
    query_output_name: &str,
) -> Result<xcb::Reply<xcb::ffi::randr::xcb_randr_get_crtc_info_reply_t>> {
    // Load screen resources of the root window
    // Return result on error
    let res_cookie = randr::get_screen_resources(conn, screen.root());
    let res_reply = res_cookie
        .get_reply()
        .map_err(|_| "Unable to get screen resources")?;

    // Get all crtcs from the reply
    let crtcs = res_reply.crtcs();

    for crtc in crtcs {
        // Get info about crtc
        let crtc_info_cookie = randr::get_crtc_info(conn, *crtc, 0);
        let crtc_info_reply = crtc_info_cookie.get_reply();

        if let Ok(reply) = crtc_info_reply {
            // Skip this crtc if it has no width or output
            if reply.width() == 0 || reply.num_outputs() == 0 {
                continue;
            }

            // Get info of crtc's first output for output name
            let output = reply.outputs()[0];
            let output_info_cookie = randr::get_output_info(conn, output, 0);
            let output_info_reply = output_info_cookie.get_reply();

            // Get the name of the first output
            let mut output_name = String::new();
            if let Ok(output_info_reply) = output_info_reply {
                output_name = String::from_utf8_lossy(output_info_reply.name()).into();
            }

            // If the output name is the requested name, return the dimensions
            if output_name == query_output_name {
                return Ok(reply);
            }
        }
    }
    let error_msg = ["Unable to find output ", query_output_name].concat();
    Err(error_msg.into())
}

/// Convinience macro for setting EWHM properites.
macro_rules! set_ewhm_prop {
    ($conn:expr, $window:expr, $name:expr, @atom $value:expr) => {
        {
            let value_atom = xcb::intern_atom($conn, true, $value)
                .get_reply().unwrap().atom();
            set_ewhm_prop!($conn, $window, $name, &[value_atom]);
        }
    };
    ($conn:expr, $window:expr, $name:expr, $data:expr) => {
        {
            let type_atom = xcb::intern_atom($conn, true, $name)
                .get_reply().unwrap().atom();
            xcb::change_property(
                $conn,
                xcb::PROP_MODE_REPLACE as u8,
                $window,
                type_atom,
                xcb::ATOM_ATOM,
                32,
                $data);
        }
    };
}

/// Creates a Xorg window using XCB.
fn create_window<'s>(
    conn: &Connection,
    screen: &Screen<'s>,
    geometry: &Rectangle,
    background: u32,
) -> Result<Window> {
    let window = conn.generate_id();

    xcb::create_window(
        &conn,
        xcb::WINDOW_CLASS_COPY_FROM_PARENT as u8,
        window,
        screen.root(),
        geometry.x(),
        geometry.y(),
        geometry.width(),
        geometry.height(),
        0,
        xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
        screen.root_visual(),
        &[
            (xcb::CW_BACK_PIXEL, background), // Default background color
            (
                xcb::CW_EVENT_MASK, // What kinds of events are we
                xcb::EVENT_MASK_EXPOSURE |       //   interested in
             xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_ENTER_WINDOW,
            ),
            (xcb::CW_OVERRIDE_REDIRECT, 0),
        ],
    );

    // TODO: Struts tell the WM to reserve space for our bar. Derive these from the Geometry struct.
    let mut strut = vec![0; 12];
    strut[2] = 30;
    strut[8] = 5;
    strut[9] = 1915;

    set_ewhm_prop!(&conn, window, "_NET_WM_WINDOW_TYPE", @atom "_NET_WM_WINDOW_TYPE_DOCK");
    set_ewhm_prop!(&conn, window, "_NET_WM_STATE", @atom "_NET_WM_STATE_STICKY");
    set_ewhm_prop!(&conn, window, "_NET_WM_DESKTOP", &[-1]);
    set_ewhm_prop!(&conn, window, "_NET_WM_STRUT_PARTIAL", strut.as_slice());
    set_ewhm_prop!(&conn, window, "_NET_WM_STRUT", &strut[0..4]);
    set_ewhm_prop!(
        &conn,
        window,
        "_NET_WM_NAME",
        "xcbars".chars().collect::<Vec<_>>().as_slice()
    ); // TODO: Allow users to define custom window title for the bar.

    // Request the WM to manage our window.
    xcb::map_window(&conn, window);

    // Some WMs (such as OpenBox) require this.
    xcb::configure_window(
        &conn,
        window,
        &[
            (xcb::CONFIG_WINDOW_X as u16, geometry.x() as u32),
            (xcb::CONFIG_WINDOW_Y as u16, geometry.y() as u32),
        ],
    );

    conn.flush();

    Ok(window)
}
<|endoftext|>