<commit_before>//!
//! # Tools
//!

use std::result::Result;
use std::error::Error;
use serde::*;
use serde_json::*;

///
/// Allows a tool to be invoked using JSON input and output
///
/// Tools in Rust are normally implemented using strong types for convenience's
/// sake, but an important capability is that their input and output can be
/// easily serialized, in particular to JSON which is currently a particularly
/// convenient format for interopability.
///
pub trait JsonTool {
    ///
    /// Invokes this tool with its input and output specified using JSON
    ///
    fn invoke_json(&self, input: Value) -> Result<Value, Value>;
}

///
/// Trait implemented by things that represent a tool
///
/// A tool is simply a routine that takes some input, does some processing
/// and returns a value or an error. The main difference between a tool and
/// a simple function is that a tool's input and output must be simple data,
/// which for Rust we define as 'can be serialized to JSON'.
///
/// Tools also have the requirement that they are encapsulated and can instantiate
/// themselves with no dependencies other than those they can discover from
/// their environment.
///
/// These two requirements mean that tools can be invoked simply by specifying
/// the input data (without necessarily having to know the exact Rust type involved!).
/// Test cases for tools can be specified as simple JSON data with no need for any
/// actual code. Tools can be turned into stand-alone command line programs or
/// web endpoints with no modification.
///
pub trait Tool<TIn: Deserialize, TOut: Serialize, TErr: Serialize> {
    ///
    /// Invokes this tool with an input data structure and returns its result
    ///
    fn invoke(&self, input: TIn) -> Result<TOut, TErr>;
}

impl<TIn, TOut, TErr> JsonTool for Tool<TIn, TOut, TErr>
where TIn: Deserialize, TOut: Serialize, TErr: Serialize {
    fn invoke_json(&self, input: Value) -> Result<Value, Value> {
        // Decode
        let input_decoded = from_value::<TIn>(input);

        // Chain into the tool itself
        match input_decoded {
            Ok(input_decoded) => {
                // Successfully decoded as the tool's input format
                let result = self.invoke(input_decoded);

                // Encode the error or the result
                // The encoding can go wrong, and we need to handle it slightly differently for the error or the success case
                match result {
                    Ok(res) => {
                        let encoded = to_value(res);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "JSON encode failed",
                                "description":  erm.description()
                            }])
                        }
                    },

                    Err(erm) => {
                        let encoded = to_value(erm);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "Error encode failed",
                                "description":  erm.description()
                            }])
                        }
                    }
                }
            },

            Err(input_error) => {
                // Input does not match the format expected by the tool
                Err(json![{
                    "error":        "JSON input decode failed",
                    "description":  input_error.description(),
                }])
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::result::Result;
    use serde::*;
    use serde_json::*;

    #[derive(Serialize, Deserialize)]
    struct TestIn {
        input: i32
    }

    #[derive(Serialize, Deserialize, PartialEq, Eq)]
    struct TestOut {
        output: i32
    }

    struct InputOutput { }
    impl Tool<TestIn, TestOut, ()> for InputOutput {
        fn invoke(&self, x: TestIn) -> Result<TestOut, ()> {
            Ok(TestOut { output: x.input+1 })
        }
    }

    #[test]
    fn can_call_tool() {
        let tool = InputOutput {};
        let result = tool.invoke(TestIn { input: 4 });

        assert!(result == Ok(TestOut { output: 5 }));
    }

    #[test]
    fn can_call_tool_via_json_interface() {
        let tool = InputOutput {};
        let result = tool.invoke_json(json![{ "input": 4 }]);

        assert!(result == Ok(json![{ "output": 5 }]));
    }
}
<commit_msg>Make JsonTool the main interface and add a way to generate a tool from a function<commit_after>//!
//! # Tools
//!

use std::result::Result;
use std::error::Error;
use serde::*;
use serde_json::*;

///
/// Trait implemented by things that represent a tool
///
/// A tool is simply a routine that takes some input, does some processing
/// and returns a value or an error. The main difference between a tool and
/// a simple function is that a tool's input and output must be simple data,
/// which for Rust we define as 'can be serialized to JSON'.
///
/// Tools also have the requirement that they are encapsulated and can instantiate
/// themselves with no dependencies other than those they can discover from
/// their environment.
///
/// These two requirements mean that tools can be invoked simply by specifying
/// the input data (without necessarily having to know the exact Rust type involved!).
/// Test cases for tools can be specified as simple JSON data with no need for any
/// actual code. Tools can be turned into stand-alone command line programs or
/// web endpoints with no modification.
///
pub trait JsonTool {
    ///
    /// Invokes this tool with its input and output specified using JSON
    ///
    fn invoke_json(&self, input: Value) -> Result<Value, Value>;
}

///
/// Represents a tool made from a function
///
pub struct FnTool<TIn: Deserialize, TOut: Serialize, TErr: Serialize> {
    function: Box<Fn(TIn) -> Result<TOut, TErr>>
}

pub fn make_tool<TIn: Deserialize, TOut: Serialize, TErr: Serialize, F: 'static+Fn(TIn) -> Result<TOut, TErr>>(function: F) -> FnTool<TIn, TOut, TErr> {
    FnTool { function: Box::new(function) }
}

impl<TIn, TOut, TErr> JsonTool for FnTool<TIn, TOut, TErr>
where TIn: Deserialize, TOut: Serialize, TErr: Serialize {
    fn invoke_json(&self, input: Value) -> Result<Value, Value> {
        // Decode
        let input_decoded = from_value::<TIn>(input);

        // Chain into the tool itself
        match input_decoded {
            Ok(input_decoded) => {
                // Successfully decoded as the tool's input format
                let result = (self.function)(input_decoded);

                // Encode the error or the result
                // The encoding can go wrong, and we need to handle it slightly differently for the error or the success case
                match result {
                    Ok(res) => {
                        let encoded = to_value(res);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "JSON encode failed",
                                "description":  erm.description()
                            }])
                        }
                    },

                    Err(erm) => {
                        let encoded = to_value(erm);
                        match encoded {
                            Ok(final_value) => Ok(final_value),
                            Err(erm)        => Err(json![{
                                "error":        "Error encode failed",
                                "description":  erm.description()
                            }])
                        }
                    }
                }
            },

            Err(input_error) => {
                // Input does not match the format expected by the tool
                Err(json![{
                    "error":        "JSON input decode failed",
                    "description":  input_error.description(),
                }])
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::result::Result;

    #[derive(Serialize, Deserialize)]
    struct TestIn {
        input: i32
    }

    #[derive(Serialize, Deserialize, PartialEq, Eq)]
    struct TestOut {
        output: i32
    }

    fn test_tool(x: TestIn) -> Result<TestOut, ()> {
        Ok(TestOut { output: x.input + 1 })
    }

    #[test]
    fn can_call_tool_via_json_interface() {
        let tool = make_tool(test_tool);
        let result = tool.invoke_json(json![{ "input": 4 }]);

        assert!(result == Ok(json![{ "output": 5 }]));
    }
}
<|endoftext|>


<commit_before>use geo;
use std::io::prelude::*;
use std::fmt;
use std::mem;
use std::u8;
use std::f64;
use byteorder::ReadBytesExt;
use error::Error;

#[derive(Debug)]
pub struct TwkbPoint {
    pub geom: geo::Point<f64>,
}

impl geo::ToGeo<f64> for TwkbPoint {
    fn to_geo(&self) -> geo::Geometry<f64> {
        geo::Geometry::Point(self.geom)
    }
}

#[derive(Debug)]
pub struct TwkbLineString {
    pub geom: geo::LineString<f64>,
}

impl geo::ToGeo<f64> for TwkbLineString {
    fn to_geo(&self) -> geo::Geometry<f64> {
        geo::Geometry::LineString(self.geom.clone())
    }
}

#[derive(Default,Debug)]
pub struct TwkbInfo {
    geom_type: u8,
    precision: i8,
    has_id_list: bool,
    is_empty_geom: bool,
    size: Option<u64>,
    has_z: bool,
    has_m: bool,
    prec_z: Option<u8>,
    prec_m: Option<u8>,
}

pub trait TwkbGeom: fmt::Debug + Sized {
    type PointType: TwkbPointType;

    fn read_twkb<R: Read>(raw: &mut R) -> Result<Self, Error> {
        // https://github.com/TWKB/Specification/blob/master/twkb.md
        let mut twkb_info: TwkbInfo = Default::default();
        // type_and_prec     byte
        // metadata_header   byte
        // [extended_dims]   byte
        // [size]            uvarint
        // [bounds]          bbox
        let type_and_prec = try!(raw.read_u8());
        twkb_info.geom_type = type_and_prec & 0x0F;
        twkb_info.precision = Self::decode_zig_zag_64(((type_and_prec & 0xF0) >> 4) as u64) as i8;
        let metadata_header = try!(raw.read_u8());
        let has_bbox = (metadata_header & 0b0001) != 0;
        let has_size_attribute = (metadata_header & 0b0010) != 0;
        twkb_info.has_id_list = (metadata_header & 0b0100) != 0;
        let has_ext_prec_info = (metadata_header & 0b1000) != 0;
        twkb_info.is_empty_geom = (metadata_header & 0b10000) != 0;
        if has_ext_prec_info {
            let ext_prec_info = try!(raw.read_u8());
            twkb_info.has_z = ext_prec_info & 0b0001 != 0;
            twkb_info.has_m = ext_prec_info & 0b0010 != 0;
            twkb_info.prec_z = Some((ext_prec_info & 0x1C) >> 2);
            twkb_info.prec_m = Some((ext_prec_info & 0xE0) >> 5);
        }
        if has_size_attribute {
            twkb_info.size = Some(try!(Self::read_raw_varint64(raw)));
        }
        if has_bbox {
            let _xmin = try!(Self::read_int64(raw));
            let _deltax = try!(Self::read_int64(raw));
            let _ymin = try!(Self::read_int64(raw));
            let _deltay = try!(Self::read_int64(raw));
            if twkb_info.has_z {
                let _zmin = try!(Self::read_int64(raw));
                let _deltaz = try!(Self::read_int64(raw));
            }
            if twkb_info.has_m {
                let _mmin = try!(Self::read_int64(raw));
                let _deltam = try!(Self::read_int64(raw));
            }
        }
        Self::read_twkb_body(raw, &twkb_info)
    }

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error>;

    fn read_raw_varint64<R: Read>(raw: &mut R) -> Result<u64, Error> {
        // from rust-protobuf
        let mut r: u64 = 0;
        let mut i = 0;
        loop {
            if i == 10 {
                return Err(Error::Read("invalid varint".into()));
            }
            let b = try!(raw.read_u8());
            // TODO: may overflow if i == 9
            r = r | (((b & 0x7f) as u64) << (i * 7));
            i += 1;
            if b < 0x80 {
                return Ok(r);
            }
        }
    }
    fn read_int64<R: Read>(raw: &mut R) -> Result<i64, Error> {
        Self::read_raw_varint64(raw).map(|v| v as i64)
    }

    fn decode_zig_zag_64(n: u64) -> i64 {
        ((n >> 1) as i64) ^ (-((n & 1) as i64))
    }

    fn varint64_to_f64(varint: u64, precision: i8) -> f64 {
        if precision >= 0 {
            (Self::decode_zig_zag_64(varint) as f64) / 10u64.pow(precision as u32) as f64
        } else {
            (Self::decode_zig_zag_64(varint) as f64) * 10u64.pow(precision.abs() as u32) as f64
        }
    }

    fn read_varint64_as_f64<R: Read>(raw: &mut R, precision: i8) -> Result<f64, Error> {
        Self::read_raw_varint64(raw).map(|v| Self::varint64_to_f64(v, precision))
    }
}

pub trait TwkbPointType: Sized {
    fn x(&self) -> f64 {
        unsafe { *mem::transmute::<_, *const f64>(self) }
    }
    fn y(&self) -> f64 {
        unsafe { *mem::transmute::<_, *const f64>(self).offset(1) }
    }
    fn opt_z(&self) -> Option<f64> {
        None
    }
    fn opt_m(&self) -> Option<f64> {
        None
    }
    fn has_z() -> bool { false }
    fn has_m() -> bool { false }

    fn new_from_opt_vals(x: f64, y: f64, z: Option<f64>, m: Option<f64>) -> Self;
}

impl TwkbPointType for TwkbPoint {
    fn new_from_opt_vals(x: f64, y: f64, _z: Option<f64>, _m: Option<f64>) -> Self {
        TwkbPoint { geom: geo::Point::new(x, y) }
    }
}

impl TwkbGeom for TwkbPoint {
    type PointType = TwkbPoint;

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error> {
        if twkb_info.is_empty_geom {
            return Ok(TwkbPoint {geom: geo::Point::new(f64::NAN, f64::NAN)});
        }
        let x = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
        let y = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
        let z = if twkb_info.has_z {
            Some(try!(Self::read_varint64_as_f64(raw, twkb_info.precision)))
        } else {
            None
        };
        let m = if twkb_info.has_m {
            Some(try!(Self::read_varint64_as_f64(raw, twkb_info.precision)))
        } else {
            None
        };
        Ok(Self::new_from_opt_vals(x, y, z, m))
    }
}

impl TwkbGeom for TwkbLineString {
    type PointType = TwkbPoint;

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error> {
        // npoints           uvarint
        // pointarray        varint[]
        let mut points: Vec<geo::Point<f64>> = vec![];
        if !twkb_info.is_empty_geom {
            let npoints = try!(Self::read_raw_varint64(raw));
            let mut x = 0.0;
            let mut y = 0.0;
            let mut z = if twkb_info.has_z { Some(0.0) } else { None };
            let mut m = if twkb_info.has_m { Some(0.0) } else { None };
            for _ in 0..npoints {
                let dx = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
                x += dx;
                let dy = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
                y += dy;
                if twkb_info.has_z {
                    let dz = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
                    z = z.map(|v| v + dz);
                };
                if twkb_info.has_m {
                    let dm = try!(Self::read_varint64_as_f64(raw, twkb_info.precision));
                    m = m.map(|v| v + dm);
                };

                points.push(TwkbPoint::new_from_opt_vals(x, y, z, m).geom);
            }
        }
        Ok(TwkbLineString {geom: geo::LineString(points)})
    }
}

#[cfg(test)]
fn hex_to_vec(hexstr: &str) -> Vec<u8> {
    hexstr.as_bytes().chunks(2).map(|chars| {
        let hb = if chars[0] <= 57 { chars[0] - 48 } else { chars[0] - 87 };
        let lb = if chars[1] <= 57 { chars[1] - 48 } else { chars[1] - 87 };
        hb * 16 + lb
    }).collect::<Vec<_>>()
}

#[test]
fn test_twkb_to_geom() {
    let twkb = hex_to_vec("01001427"); // SELECT encode(ST_AsTWKB('POINT(10 -20)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("0108011427c601"); // SELECT encode(ST_AsTWKB('POINT(10 -20 99)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("2100ca019503"); // SELECT encode(ST_AsTWKB('POINT(10.12 -20.34)'::geometry, 1), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10.1, y: -20.3 })");

    let twkb = hex_to_vec("11000203"); // SELECT encode(ST_AsTWKB('POINT(11.12 -22.34)'::geometry, -1), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("0110"); // SELECT encode(ST_AsTWKB('POINT EMPTY'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: NaN, y: NaN })");

    let twkb = hex_to_vec("a10080897aff91f401"); // SELECT encode(ST_AsTWKB('SRID=4326;POINT(10 -20)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("02000214271326"); // SELECT encode(ST_AsTWKB('LINESTRING (10 -20, -0 -0.5)'::geometry), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([Point(Coordinate { x: 10, y: -20 }), Point(Coordinate { x: 0, y: -1 })])");

    let twkb = hex_to_vec("220002c8018f03c7018603"); // SELECT encode(ST_AsTWKB('LINESTRING (10 -20, -0 -0.5)'::geometry, 1), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([Point(Coordinate { x: 10, y: -20 }), Point(Coordinate { x: 0, y: -0.5 })])");

    let twkb = hex_to_vec("0210"); // SELECT encode(ST_AsTWKB('LINESTRING EMPTY'::geometry), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([])");
}
<commit_msg>Move TWKB reader helper functions to module level<commit_after>use geo;
use std::io::prelude::*;
use std::fmt;
use std::mem;
use std::u8;
use std::f64;
use byteorder::ReadBytesExt;
use error::Error;

#[derive(Debug)]
pub struct TwkbPoint {
    pub geom: geo::Point<f64>,
}

impl geo::ToGeo<f64> for TwkbPoint {
    fn to_geo(&self) -> geo::Geometry<f64> {
        geo::Geometry::Point(self.geom)
    }
}

#[derive(Debug)]
pub struct TwkbLineString {
    pub geom: geo::LineString<f64>,
}

impl geo::ToGeo<f64> for TwkbLineString {
    fn to_geo(&self) -> geo::Geometry<f64> {
        geo::Geometry::LineString(self.geom.clone())
    }
}

#[derive(Default,Debug)]
pub struct TwkbInfo {
    geom_type: u8,
    precision: i8,
    has_id_list: bool,
    is_empty_geom: bool,
    size: Option<u64>,
    has_z: bool,
    has_m: bool,
    prec_z: Option<u8>,
    prec_m: Option<u8>,
}

pub trait TwkbGeom: fmt::Debug + Sized {
    type PointType: TwkbPointType;

    fn read_twkb<R: Read>(raw: &mut R) -> Result<Self, Error> {
        // https://github.com/TWKB/Specification/blob/master/twkb.md
        let mut twkb_info: TwkbInfo = Default::default();
        // type_and_prec     byte
        // metadata_header   byte
        // [extended_dims]   byte
        // [size]            uvarint
        // [bounds]          bbox
        let type_and_prec = try!(raw.read_u8());
        twkb_info.geom_type = type_and_prec & 0x0F;
        twkb_info.precision = decode_zig_zag_64(((type_and_prec & 0xF0) >> 4) as u64) as i8;
        let metadata_header = try!(raw.read_u8());
        let has_bbox = (metadata_header & 0b0001) != 0;
        let has_size_attribute = (metadata_header & 0b0010) != 0;
        twkb_info.has_id_list = (metadata_header & 0b0100) != 0;
        let has_ext_prec_info = (metadata_header & 0b1000) != 0;
        twkb_info.is_empty_geom = (metadata_header & 0b10000) != 0;
        if has_ext_prec_info {
            let ext_prec_info = try!(raw.read_u8());
            twkb_info.has_z = ext_prec_info & 0b0001 != 0;
            twkb_info.has_m = ext_prec_info & 0b0010 != 0;
            twkb_info.prec_z = Some((ext_prec_info & 0x1C) >> 2);
            twkb_info.prec_m = Some((ext_prec_info & 0xE0) >> 5);
        }
        if has_size_attribute {
            twkb_info.size = Some(try!(read_raw_varint64(raw)));
        }
        if has_bbox {
            let _xmin = try!(read_int64(raw));
            let _deltax = try!(read_int64(raw));
            let _ymin = try!(read_int64(raw));
            let _deltay = try!(read_int64(raw));
            if twkb_info.has_z {
                let _zmin = try!(read_int64(raw));
                let _deltaz = try!(read_int64(raw));
            }
            if twkb_info.has_m {
                let _mmin = try!(read_int64(raw));
                let _deltam = try!(read_int64(raw));
            }
        }
        Self::read_twkb_body(raw, &twkb_info)
    }

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error>;
}

// --- helper functions for reading ---

fn read_raw_varint64<R: Read>(raw: &mut R) -> Result<u64, Error> {
    // from rust-protobuf
    let mut r: u64 = 0;
    let mut i = 0;
    loop {
        if i == 10 {
            return Err(Error::Read("invalid varint".into()));
        }
        let b = try!(raw.read_u8());
        // TODO: may overflow if i == 9
        r = r | (((b & 0x7f) as u64) << (i * 7));
        i += 1;
        if b < 0x80 {
            return Ok(r);
        }
    }
}

fn read_int64<R: Read>(raw: &mut R) -> Result<i64, Error> {
    read_raw_varint64(raw).map(|v| v as i64)
}

fn decode_zig_zag_64(n: u64) -> i64 {
    ((n >> 1) as i64) ^ (-((n & 1) as i64))
}

fn varint64_to_f64(varint: u64, precision: i8) -> f64 {
    if precision >= 0 {
        (decode_zig_zag_64(varint) as f64) / 10u64.pow(precision as u32) as f64
    } else {
        (decode_zig_zag_64(varint) as f64) * 10u64.pow(precision.abs() as u32) as f64
    }
}

fn read_varint64_as_f64<R: Read>(raw: &mut R, precision: i8) -> Result<f64, Error> {
    read_raw_varint64(raw).map(|v| varint64_to_f64(v, precision))
}

// ---

pub trait TwkbPointType: Sized {
    fn x(&self) -> f64 {
        unsafe { *mem::transmute::<_, *const f64>(self) }
    }
    fn y(&self) -> f64 {
        unsafe { *mem::transmute::<_, *const f64>(self).offset(1) }
    }
    fn opt_z(&self) -> Option<f64> {
        None
    }
    fn opt_m(&self) -> Option<f64> {
        None
    }
    fn has_z() -> bool { false }
    fn has_m() -> bool { false }

    fn new_from_opt_vals(x: f64, y: f64, z: Option<f64>, m: Option<f64>) -> Self;
}

impl TwkbPointType for TwkbPoint {
    fn new_from_opt_vals(x: f64, y: f64, _z: Option<f64>, _m: Option<f64>) -> Self {
        TwkbPoint { geom: geo::Point::new(x, y) }
    }
}

impl TwkbGeom for TwkbPoint {
    type PointType = TwkbPoint;

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error> {
        if twkb_info.is_empty_geom {
            return Ok(TwkbPoint {geom: geo::Point::new(f64::NAN, f64::NAN)});
        }
        let x = try!(read_varint64_as_f64(raw, twkb_info.precision));
        let y = try!(read_varint64_as_f64(raw, twkb_info.precision));
        let z = if twkb_info.has_z {
            Some(try!(read_varint64_as_f64(raw, twkb_info.precision)))
        } else {
            None
        };
        let m = if twkb_info.has_m {
            Some(try!(read_varint64_as_f64(raw, twkb_info.precision)))
        } else {
            None
        };
        Ok(Self::new_from_opt_vals(x, y, z, m))
    }
}

impl TwkbGeom for TwkbLineString {
    type PointType = TwkbPoint;

    fn read_twkb_body<R: Read>(raw: &mut R, twkb_info: &TwkbInfo) -> Result<Self, Error> {
        // npoints           uvarint
        // pointarray        varint[]
        let mut points: Vec<geo::Point<f64>> = vec![];
        if !twkb_info.is_empty_geom {
            let npoints = try!(read_raw_varint64(raw));
            let mut x = 0.0;
            let mut y = 0.0;
            let mut z = if twkb_info.has_z { Some(0.0) } else { None };
            let mut m = if twkb_info.has_m { Some(0.0) } else { None };
            for _ in 0..npoints {
                let dx = try!(read_varint64_as_f64(raw, twkb_info.precision));
                x += dx;
                let dy = try!(read_varint64_as_f64(raw, twkb_info.precision));
                y += dy;
                if twkb_info.has_z {
                    let dz = try!(read_varint64_as_f64(raw, twkb_info.precision));
                    z = z.map(|v| v + dz);
                };
                if twkb_info.has_m {
                    let dm = try!(read_varint64_as_f64(raw, twkb_info.precision));
                    m = m.map(|v| v + dm);
                };

                points.push(TwkbPoint::new_from_opt_vals(x, y, z, m).geom);
            }
        }
        Ok(TwkbLineString {geom: geo::LineString(points)})
    }
}

#[cfg(test)]
fn hex_to_vec(hexstr: &str) -> Vec<u8> {
    hexstr.as_bytes().chunks(2).map(|chars| {
        let hb = if chars[0] <= 57 { chars[0] - 48 } else { chars[0] - 87 };
        let lb = if chars[1] <= 57 { chars[1] - 48 } else { chars[1] - 87 };
        hb * 16 + lb
    }).collect::<Vec<_>>()
}

#[test]
fn test_twkb_to_geom() {
    let twkb = hex_to_vec("01001427"); // SELECT encode(ST_AsTWKB('POINT(10 -20)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("0108011427c601"); // SELECT encode(ST_AsTWKB('POINT(10 -20 99)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("2100ca019503"); // SELECT encode(ST_AsTWKB('POINT(10.12 -20.34)'::geometry, 1), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10.1, y: -20.3 })");

    let twkb = hex_to_vec("11000203"); // SELECT encode(ST_AsTWKB('POINT(11.12 -22.34)'::geometry, -1), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("0110"); // SELECT encode(ST_AsTWKB('POINT EMPTY'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: NaN, y: NaN })");

    let twkb = hex_to_vec("a10080897aff91f401"); // SELECT encode(ST_AsTWKB('SRID=4326;POINT(10 -20)'::geometry), 'hex')
    let point = TwkbPoint::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", point.geom), "Point(Coordinate { x: 10, y: -20 })");

    let twkb = hex_to_vec("02000214271326"); // SELECT encode(ST_AsTWKB('LINESTRING (10 -20, -0 -0.5)'::geometry), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([Point(Coordinate { x: 10, y: -20 }), Point(Coordinate { x: 0, y: -1 })])");

    let twkb = hex_to_vec("220002c8018f03c7018603"); // SELECT encode(ST_AsTWKB('LINESTRING (10 -20, -0 -0.5)'::geometry, 1), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([Point(Coordinate { x: 10, y: -20 }), Point(Coordinate { x: 0, y: -0.5 })])");

    let twkb = hex_to_vec("0210"); // SELECT encode(ST_AsTWKB('LINESTRING EMPTY'::geometry), 'hex')
    let line = TwkbLineString::read_twkb(&mut twkb.as_slice()).unwrap();
    assert_eq!(format!("{:?}", line.geom), "LineString([])");
}
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;

use anyhow::{anyhow, Result};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::bytes::Regex;

use crate::error::print_error;
use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::filesystem;
use crate::options::Options;
use crate::output;

/// The receiver thread can either be buffering results or directly streaming to the console.
enum ReceiverMode {
    /// Receiver is still buffering in order to sort the results, if the search finishes fast
    /// enough.
    Buffering,

    /// Receiver is directly printing results to the output.
    Streaming,
}

/// The Worker threads can result in a valid entry having PathBuf or an error.
pub enum WorkerResult {
    Entry(PathBuf),
    Error(ignore::Error),
}

/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;

/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) -> Result<ExitCode> {
    let mut path_iter = path_vec.iter();
    let first_path_buf = path_iter
        .next()
        .expect("Error: Path vector can not be empty");
    let (tx, rx) = channel();

    let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());

    for pattern in &config.exclude_patterns {
        override_builder
            .add(pattern)
            .map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
    }
    let overrides = override_builder
        .build()
        .map_err(|_| anyhow!("Mismatch in exclude patterns"))?;

    let mut walker = WalkBuilder::new(first_path_buf.as_path());
    walker
        .hidden(config.ignore_hidden)
        .ignore(config.read_fdignore)
        .parents(config.read_fdignore || config.read_vcsignore)
        .git_ignore(config.read_vcsignore)
        .git_global(config.read_vcsignore)
        .git_exclude(config.read_vcsignore)
        .overrides(overrides)
        .follow_links(config.follow_links)
        // No need to check for supported platforms, option is unavailable on unsupported ones
        .same_file_system(config.one_file_system)
        .max_depth(config.max_depth);

    if config.read_fdignore {
        walker.add_custom_ignore_filename(".fdignore");
    }

    if config.read_global_ignore {
        #[cfg(target_os = "macos")]
        let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
            .map(PathBuf::from)
            .filter(|p| p.is_absolute())
            .or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));

        #[cfg(not(target_os = "macos"))]
        let config_dir_op = dirs_next::config_dir();

        if let Some(global_ignore_file) = config_dir_op
            .map(|p| p.join("fd").join("ignore"))
            .filter(|p| p.is_file())
        {
            let result = walker.add_ignore(global_ignore_file);
            match result {
                Some(ignore::Error::Partial(_)) => (),
                Some(err) => {
                    print_error(format!(
                        "Malformed pattern in global ignore file. {}.",
                        err.to_string()
                    ));
                }
                None => (),
            }
        }
    }

    for ignore_file in &config.ignore_files {
        let result = walker.add_ignore(ignore_file);
        match result {
            Some(ignore::Error::Partial(_)) => (),
            Some(err) => {
                print_error(format!(
                    "Malformed pattern in custom ignore file. {}.",
                    err.to_string()
                ));
            }
            None => (),
        }
    }

    for path_entry in path_iter {
        walker.add(path_entry.as_path());
    }

    let parallel_walker = walker.threads(config.threads).build_parallel();

    let wants_to_quit = Arc::new(AtomicBool::new(false));
    if config.ls_colors.is_some() && config.command.is_none() {
        let wq = Arc::clone(&wants_to_quit);
        ctrlc::set_handler(move || {
            if wq.load(Ordering::Relaxed) {
                // Ctrl-C has been pressed twice, exit NOW
                process::exit(ExitCode::KilledBySigint.into());
            } else {
                wq.store(true, Ordering::Relaxed);
            }
        })
        .unwrap();
    }

    // Spawn the thread that receives all results through the channel.
    let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);

    // Spawn the sender threads.
    spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);

    // Wait for the receiver thread to print out all results.
    let exit_code = receiver_thread.join().unwrap();

    if wants_to_quit.load(Ordering::Relaxed) {
        Ok(ExitCode::KilledBySigint)
    } else {
        Ok(exit_code)
    }
}

fn spawn_receiver(
    config: &Arc<Options>,
    wants_to_quit: &Arc<AtomicBool>,
    rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<ExitCode> {
    let config = Arc::clone(config);
    let wants_to_quit = Arc::clone(wants_to_quit);

    let show_filesystem_errors = config.show_filesystem_errors;
    let threads = config.threads;

    thread::spawn(move || {
        // This will be set to `Some` if the `--exec` argument was supplied.
        if let Some(ref cmd) = config.command {
            if cmd.in_batch_mode() {
                exec::batch(rx, cmd, show_filesystem_errors)
            } else {
                let shared_rx = Arc::new(Mutex::new(rx));

                let out_perm = Arc::new(Mutex::new(()));

                // Each spawned job will store it's thread handle in here.
                let mut handles = Vec::with_capacity(threads);
                for _ in 0..threads {
                    let rx = Arc::clone(&shared_rx);
                    let cmd = Arc::clone(cmd);
                    let out_perm = Arc::clone(&out_perm);

                    // Spawn a job thread that will listen for and execute inputs.
                    let handle =
                        thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));

                    // Push the handle of the spawned thread into the vector for later joining.
                    handles.push(handle);
                }

                // Wait for all threads to exit before exiting the program.
                let mut results: Vec<ExitCode> = Vec::new();
                for h in handles {
                    results.push(h.join().unwrap());
                }

                merge_exitcodes(&results)
            }
        } else {
            let start = time::Instant::now();

            let mut buffer = vec![];

            // Start in buffering mode
            let mut mode = ReceiverMode::Buffering;

            // Maximum time to wait before we start streaming to the console.
            let max_buffer_time = config
                .max_buffer_time
                .unwrap_or_else(|| time::Duration::from_millis(100));

            let stdout = io::stdout();
            let mut stdout = stdout.lock();

            let mut num_results = 0;

            for worker_result in rx {
                match worker_result {
                    WorkerResult::Entry(value) => {
                        match mode {
                            ReceiverMode::Buffering => {
                                buffer.push(value);

                                // Have we reached the maximum buffer size or maximum buffering time?
                                if buffer.len() > MAX_BUFFER_LENGTH
                                    || time::Instant::now() - start > max_buffer_time
                                {
                                    // Flush the buffer
                                    for v in &buffer {
                                        output::print_entry(
                                            &mut stdout,
                                            v,
                                            &config,
                                            &wants_to_quit,
                                        );
                                    }
                                    buffer.clear();

                                    // Start streaming
                                    mode = ReceiverMode::Streaming;
                                }
                            }
                            ReceiverMode::Streaming => {
                                output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
                            }
                        }

                        num_results += 1;
                    }
                    WorkerResult::Error(err) => {
                        if show_filesystem_errors {
                            print_error(err.to_string());
                        }
                    }
                }

                if let Some(max_results) = config.max_results {
                    if num_results >= max_results {
                        break;
                    }
                }
            }

            // If we have finished fast enough (faster than max_buffer_time), we haven't streamed
            // anything to the console, yet. In this case, sort the results and print them:
            if !buffer.is_empty() {
                buffer.sort();
                for value in buffer {
                    output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
                }
            }

            ExitCode::Success
        }
    })
}

pub enum DirEntry {
    Normal(ignore::DirEntry),
    BrokenSymlink(PathBuf),
}

impl DirEntry {
    pub fn path(&self) -> &Path {
        match self {
            DirEntry::Normal(e) => e.path(),
            DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(),
        }
    }

    pub fn file_type(&self) -> Option<FileType> {
        match self {
            DirEntry::Normal(e) => e.file_type(),
            DirEntry::BrokenSymlink(pathbuf) => {
                pathbuf.symlink_metadata().map(|m| m.file_type()).ok()
            }
        }
    }

    pub fn metadata(&self) -> Option<Metadata> {
        match self {
            DirEntry::Normal(e) => e.metadata().ok(),
            DirEntry::BrokenSymlink(_) => None,
        }
    }

    pub fn depth(&self) -> Option<usize> {
        match self {
            DirEntry::Normal(e) => Some(e.depth()),
            DirEntry::BrokenSymlink(_) => None,
        }
    }
}

fn spawn_senders(
    config: &Arc<Options>,
    wants_to_quit: &Arc<AtomicBool>,
    pattern: Arc<Regex>,
    parallel_walker: ignore::WalkParallel,
    tx: Sender<WorkerResult>,
) {
    parallel_walker.run(|| {
        let config = Arc::clone(config);
        let pattern = Arc::clone(&pattern);
        let tx_thread = tx.clone();
        let wants_to_quit = Arc::clone(wants_to_quit);

        Box::new(move |entry_o| {
            if wants_to_quit.load(Ordering::Relaxed) {
                return ignore::WalkState::Quit;
            }

            let entry = match entry_o {
                Ok(ref e) if e.depth() == 0 => {
                    // Skip the root directory entry.
                    return ignore::WalkState::Continue;
                }
                Ok(e) => DirEntry::Normal(e),
                Err(ignore::Error::WithPath {
                    path,
                    err: inner_err,
                }) => match inner_err.as_ref() {
                    ignore::Error::Io(io_error)
                        if io_error.kind() == io::ErrorKind::NotFound
                            && path
                                .symlink_metadata()
                                .ok()
                                .map_or(false, |m| m.file_type().is_symlink()) =>
                    {
                        DirEntry::BrokenSymlink(path)
                    }
                    _ => {
                        match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
                            path,
                            err: inner_err,
                        })) {
                            Ok(_) => {
                                return ignore::WalkState::Continue;
                            }
                            Err(_) => {
                                return ignore::WalkState::Quit;
                            }
                        }
                    }
                },
                Err(err) => match tx_thread.send(WorkerResult::Error(err)) {
                    Ok(_) => {
                        return ignore::WalkState::Continue;
                    }
                    Err(_) => {
                        return ignore::WalkState::Quit;
                    }
                },
            };

            if let Some(min_depth) = config.min_depth {
                if entry.depth().map_or(true, |d| d < min_depth) {
                    return ignore::WalkState::Continue;
                }
            }

            // Check the name first, since it doesn't require metadata
            let entry_path = entry.path();

            let search_str: Cow<OsStr> = if config.search_full_path {
                let path_abs_buf = filesystem::path_absolute_form(entry_path)
                    .expect("Retrieving absolute path succeeds");
                Cow::Owned(path_abs_buf.as_os_str().to_os_string())
            } else {
                match entry_path.file_name() {
                    Some(filename) => Cow::Borrowed(filename),
                    None => unreachable!(
                        "Encountered file system entry without a file name. This should only \
                         happen for paths like 'foo/bar/..' or '/' which are not supposed to \
                         appear in a file system traversal."
                    ),
                }
            };

            if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
                return ignore::WalkState::Continue;
            }

            // Filter out unwanted extensions.
            if let Some(ref exts_regex) = config.extensions {
                if let Some(path_str) = entry_path.file_name() {
                    if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            // Filter out unwanted file types.
            if let Some(ref file_types) = config.file_types {
                if let Some(ref entry_type) = entry.file_type() {
                    if (!file_types.files && entry_type.is_file())
                        || (!file_types.directories && entry_type.is_dir())
                        || (!file_types.symlinks && entry_type.is_symlink())
                        || (!file_types.sockets && filesystem::is_socket(*entry_type))
                        || (!file_types.pipes && filesystem::is_pipe(*entry_type))
                        || (file_types.executables_only
                            && !entry
                                .metadata()
                                .map(|m| filesystem::is_executable(&m))
                                .unwrap_or(false))
                        || (file_types.empty_only && !filesystem::is_empty(&entry))
                        || !(entry_type.is_file()
                            || entry_type.is_dir()
                            || entry_type.is_symlink()
                            || filesystem::is_socket(*entry_type)
                            || filesystem::is_pipe(*entry_type))
                    {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            #[cfg(unix)]
            {
                if let Some(ref owner_constraint) = config.owner_constraint {
                    if let Ok(ref metadata) = entry_path.metadata() {
                        if !owner_constraint.matches(metadata) {
                            return ignore::WalkState::Continue;
                        }
                    } else {
                        return ignore::WalkState::Continue;
                    }
                }
            }

            // Filter out unwanted sizes if it is a file and we have been given size constraints.
            if !config.size_constraints.is_empty() {
                if entry_path.is_file() {
                    if let Ok(metadata) = entry_path.metadata() {
                        let file_size = metadata.len();
                        if config
                            .size_constraints
                            .iter()
                            .any(|sc| !sc.is_within(file_size))
                        {
                            return ignore::WalkState::Continue;
                        }
                    } else {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            // Filter out unwanted modification times
            if !config.time_constraints.is_empty() {
                let mut matched = false;
                if let Ok(metadata) = entry_path.metadata() {
                    if let Ok(modified) = metadata.modified() {
                        matched = config
                            .time_constraints
                            .iter()
                            .all(|tf| tf.applies_to(&modified));
                    }
                }
                if !matched {
                    return ignore::WalkState::Continue;
                }
            }

            let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));

            if send_result.is_err() {
                return ignore::WalkState::Quit;
            }

            // Apply pruning.
            if config.prune {
                return ignore::WalkState::Skip;
            }

            ignore::WalkState::Continue
        })
    });
}
<commit_msg>Fix return out of match pattern<commit_after>use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;

use anyhow::{anyhow, Result};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::bytes::Regex;

use crate::error::print_error;
use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::filesystem;
use crate::options::Options;
use crate::output;

/// The receiver thread can either be buffering results or directly streaming to the console.
enum ReceiverMode {
    /// Receiver is still buffering in order to sort the results, if the search finishes fast
    /// enough.
    Buffering,

    /// Receiver is directly printing results to the output.
    Streaming,
}

/// The Worker threads can result in a valid entry having PathBuf or an error.
pub enum WorkerResult {
    Entry(PathBuf),
    Error(ignore::Error),
}

/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;

/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) -> Result<ExitCode> {
    let mut path_iter = path_vec.iter();
    let first_path_buf = path_iter
        .next()
        .expect("Error: Path vector can not be empty");
    let (tx, rx) = channel();

    let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());

    for pattern in &config.exclude_patterns {
        override_builder
            .add(pattern)
            .map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
    }
    let overrides = override_builder
        .build()
        .map_err(|_| anyhow!("Mismatch in exclude patterns"))?;

    let mut walker = WalkBuilder::new(first_path_buf.as_path());
    walker
        .hidden(config.ignore_hidden)
        .ignore(config.read_fdignore)
        .parents(config.read_fdignore || config.read_vcsignore)
        .git_ignore(config.read_vcsignore)
        .git_global(config.read_vcsignore)
        .git_exclude(config.read_vcsignore)
        .overrides(overrides)
        .follow_links(config.follow_links)
        // No need to check for supported platforms, option is unavailable on unsupported ones
        .same_file_system(config.one_file_system)
        .max_depth(config.max_depth);

    if config.read_fdignore {
        walker.add_custom_ignore_filename(".fdignore");
    }

    if config.read_global_ignore {
        #[cfg(target_os = "macos")]
        let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
            .map(PathBuf::from)
            .filter(|p| p.is_absolute())
            .or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));

        #[cfg(not(target_os = "macos"))]
        let config_dir_op = dirs_next::config_dir();

        if let Some(global_ignore_file) = config_dir_op
            .map(|p| p.join("fd").join("ignore"))
            .filter(|p| p.is_file())
        {
            let result = walker.add_ignore(global_ignore_file);
            match result {
                Some(ignore::Error::Partial(_)) => (),
                Some(err) => {
                    print_error(format!(
                        "Malformed pattern in global ignore file. {}.",
                        err.to_string()
                    ));
                }
                None => (),
            }
        }
    }

    for ignore_file in &config.ignore_files {
        let result = walker.add_ignore(ignore_file);
        match result {
            Some(ignore::Error::Partial(_)) => (),
            Some(err) => {
                print_error(format!(
                    "Malformed pattern in custom ignore file. {}.",
                    err.to_string()
                ));
            }
            None => (),
        }
    }

    for path_entry in path_iter {
        walker.add(path_entry.as_path());
    }

    let parallel_walker = walker.threads(config.threads).build_parallel();

    let wants_to_quit = Arc::new(AtomicBool::new(false));
    if config.ls_colors.is_some() && config.command.is_none() {
        let wq = Arc::clone(&wants_to_quit);
        ctrlc::set_handler(move || {
            if wq.load(Ordering::Relaxed) {
                // Ctrl-C has been pressed twice, exit NOW
                process::exit(ExitCode::KilledBySigint.into());
            } else {
                wq.store(true, Ordering::Relaxed);
            }
        })
        .unwrap();
    }

    // Spawn the thread that receives all results through the channel.
    let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);

    // Spawn the sender threads.
    spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);

    // Wait for the receiver thread to print out all results.
    let exit_code = receiver_thread.join().unwrap();

    if wants_to_quit.load(Ordering::Relaxed) {
        Ok(ExitCode::KilledBySigint)
    } else {
        Ok(exit_code)
    }
}

fn spawn_receiver(
    config: &Arc<Options>,
    wants_to_quit: &Arc<AtomicBool>,
    rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<ExitCode> {
    let config = Arc::clone(config);
    let wants_to_quit = Arc::clone(wants_to_quit);

    let show_filesystem_errors = config.show_filesystem_errors;
    let threads = config.threads;

    thread::spawn(move || {
        // This will be set to `Some` if the `--exec` argument was supplied.
        if let Some(ref cmd) = config.command {
            if cmd.in_batch_mode() {
                exec::batch(rx, cmd, show_filesystem_errors)
            } else {
                let shared_rx = Arc::new(Mutex::new(rx));

                let out_perm = Arc::new(Mutex::new(()));

                // Each spawned job will store it's thread handle in here.
                let mut handles = Vec::with_capacity(threads);
                for _ in 0..threads {
                    let rx = Arc::clone(&shared_rx);
                    let cmd = Arc::clone(cmd);
                    let out_perm = Arc::clone(&out_perm);

                    // Spawn a job thread that will listen for and execute inputs.
                    let handle =
                        thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));

                    // Push the handle of the spawned thread into the vector for later joining.
                    handles.push(handle);
                }

                // Wait for all threads to exit before exiting the program.
                let mut results: Vec<ExitCode> = Vec::new();
                for h in handles {
                    results.push(h.join().unwrap());
                }

                merge_exitcodes(&results)
            }
        } else {
            let start = time::Instant::now();

            let mut buffer = vec![];

            // Start in buffering mode
            let mut mode = ReceiverMode::Buffering;

            // Maximum time to wait before we start streaming to the console.
            let max_buffer_time = config
                .max_buffer_time
                .unwrap_or_else(|| time::Duration::from_millis(100));

            let stdout = io::stdout();
            let mut stdout = stdout.lock();

            let mut num_results = 0;

            for worker_result in rx {
                match worker_result {
                    WorkerResult::Entry(value) => {
                        match mode {
                            ReceiverMode::Buffering => {
                                buffer.push(value);

                                // Have we reached the maximum buffer size or maximum buffering time?
                                if buffer.len() > MAX_BUFFER_LENGTH
                                    || time::Instant::now() - start > max_buffer_time
                                {
                                    // Flush the buffer
                                    for v in &buffer {
                                        output::print_entry(
                                            &mut stdout,
                                            v,
                                            &config,
                                            &wants_to_quit,
                                        );
                                    }
                                    buffer.clear();

                                    // Start streaming
                                    mode = ReceiverMode::Streaming;
                                }
                            }
                            ReceiverMode::Streaming => {
                                output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
                            }
                        }

                        num_results += 1;
                    }
                    WorkerResult::Error(err) => {
                        if show_filesystem_errors {
                            print_error(err.to_string());
                        }
                    }
                }

                if let Some(max_results) = config.max_results {
                    if num_results >= max_results {
                        break;
                    }
                }
            }

            // If we have finished fast enough (faster than max_buffer_time), we haven't streamed
            // anything to the console, yet. In this case, sort the results and print them:
            if !buffer.is_empty() {
                buffer.sort();
                for value in buffer {
                    output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
                }
            }

            ExitCode::Success
        }
    })
}

pub enum DirEntry {
    Normal(ignore::DirEntry),
    BrokenSymlink(PathBuf),
}

impl DirEntry {
    pub fn path(&self) -> &Path {
        match self {
            DirEntry::Normal(e) => e.path(),
            DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(),
        }
    }

    pub fn file_type(&self) -> Option<FileType> {
        match self {
            DirEntry::Normal(e) => e.file_type(),
            DirEntry::BrokenSymlink(pathbuf) => {
                pathbuf.symlink_metadata().map(|m| m.file_type()).ok()
            }
        }
    }

    pub fn metadata(&self) -> Option<Metadata> {
        match self {
            DirEntry::Normal(e) => e.metadata().ok(),
            DirEntry::BrokenSymlink(_) => None,
        }
    }

    pub fn depth(&self) -> Option<usize> {
        match self {
            DirEntry::Normal(e) => Some(e.depth()),
            DirEntry::BrokenSymlink(_) => None,
        }
    }
}

fn spawn_senders(
    config: &Arc<Options>,
    wants_to_quit: &Arc<AtomicBool>,
    pattern: Arc<Regex>,
    parallel_walker: ignore::WalkParallel,
    tx: Sender<WorkerResult>,
) {
    parallel_walker.run(|| {
        let config = Arc::clone(config);
        let pattern = Arc::clone(&pattern);
        let tx_thread = tx.clone();
        let wants_to_quit = Arc::clone(wants_to_quit);

        Box::new(move |entry_o| {
            if wants_to_quit.load(Ordering::Relaxed) {
                return ignore::WalkState::Quit;
            }

            let entry = match entry_o {
                Ok(ref e) if e.depth() == 0 => {
                    // Skip the root directory entry.
                    return ignore::WalkState::Continue;
                }
                Ok(e) => DirEntry::Normal(e),
                Err(ignore::Error::WithPath {
                    path,
                    err: inner_err,
                }) => match inner_err.as_ref() {
                    ignore::Error::Io(io_error)
                        if io_error.kind() == io::ErrorKind::NotFound
                            && path
                                .symlink_metadata()
                                .ok()
                                .map_or(false, |m| m.file_type().is_symlink()) =>
                    {
                        DirEntry::BrokenSymlink(path)
                    }
                    _ => {
                        return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
                            path,
                            err: inner_err,
                        })) {
                            Ok(_) => ignore::WalkState::Continue,
                            Err(_) => ignore::WalkState::Quit,
                        }
                    }
                },
                Err(err) => {
                    return match tx_thread.send(WorkerResult::Error(err)) {
                        Ok(_) => ignore::WalkState::Continue,
                        Err(_) => ignore::WalkState::Quit,
                    }
                }
            };

            if let Some(min_depth) = config.min_depth {
                if entry.depth().map_or(true, |d| d < min_depth) {
                    return ignore::WalkState::Continue;
                }
            }

            // Check the name first, since it doesn't require metadata
            let entry_path = entry.path();

            let search_str: Cow<OsStr> = if config.search_full_path {
                let path_abs_buf = filesystem::path_absolute_form(entry_path)
                    .expect("Retrieving absolute path succeeds");
                Cow::Owned(path_abs_buf.as_os_str().to_os_string())
            } else {
                match entry_path.file_name() {
                    Some(filename) => Cow::Borrowed(filename),
                    None => unreachable!(
                        "Encountered file system entry without a file name. This should only \
                         happen for paths like 'foo/bar/..' or '/' which are not supposed to \
                         appear in a file system traversal."
                    ),
                }
            };

            if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
                return ignore::WalkState::Continue;
            }

            // Filter out unwanted extensions.
            if let Some(ref exts_regex) = config.extensions {
                if let Some(path_str) = entry_path.file_name() {
                    if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            // Filter out unwanted file types.
            if let Some(ref file_types) = config.file_types {
                if let Some(ref entry_type) = entry.file_type() {
                    if (!file_types.files && entry_type.is_file())
                        || (!file_types.directories && entry_type.is_dir())
                        || (!file_types.symlinks && entry_type.is_symlink())
                        || (!file_types.sockets && filesystem::is_socket(*entry_type))
                        || (!file_types.pipes && filesystem::is_pipe(*entry_type))
                        || (file_types.executables_only
                            && !entry
                                .metadata()
                                .map(|m| filesystem::is_executable(&m))
                                .unwrap_or(false))
                        || (file_types.empty_only && !filesystem::is_empty(&entry))
                        || !(entry_type.is_file()
                            || entry_type.is_dir()
                            || entry_type.is_symlink()
                            || filesystem::is_socket(*entry_type)
                            || filesystem::is_pipe(*entry_type))
                    {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            #[cfg(unix)]
            {
                if let Some(ref owner_constraint) = config.owner_constraint {
                    if let Ok(ref metadata) = entry_path.metadata() {
                        if !owner_constraint.matches(metadata) {
                            return ignore::WalkState::Continue;
                        }
                    } else {
                        return ignore::WalkState::Continue;
                    }
                }
            }

            // Filter out unwanted sizes if it is a file and we have been given size constraints.
            if !config.size_constraints.is_empty() {
                if entry_path.is_file() {
                    if let Ok(metadata) = entry_path.metadata() {
                        let file_size = metadata.len();
                        if config
                            .size_constraints
                            .iter()
                            .any(|sc| !sc.is_within(file_size))
                        {
                            return ignore::WalkState::Continue;
                        }
                    } else {
                        return ignore::WalkState::Continue;
                    }
                } else {
                    return ignore::WalkState::Continue;
                }
            }

            // Filter out unwanted modification times
            if !config.time_constraints.is_empty() {
                let mut matched = false;
                if let Ok(metadata) = entry_path.metadata() {
                    if let Ok(modified) = metadata.modified() {
                        matched = config
                            .time_constraints
                            .iter()
                            .all(|tf| tf.applies_to(&modified));
                    }
                }
                if !matched {
                    return ignore::WalkState::Continue;
                }
            }

            let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));

            if send_result.is_err() {
                return ignore::WalkState::Quit;
            }

            // Apply pruning.
            if config.prune {
                return ignore::WalkState::Skip;
            }

            ignore::WalkState::Continue
        })
    });
}
<|endoftext|>


<commit_before>//! Abstractions for the replies passed back from i3.

use std::collections::HashMap;

/// The outcome of a single command.
#[derive(Debug)]
pub struct CommandOutcome {
    /// Whether the command was successful.
    pub success: bool,
    /// A human-readable error message.
    pub error: Option<String>,
}

/// The reply to the `command` request.
#[derive(Debug)]
pub struct Command {
    /// A list of `CommandOutcome` structs; one for each command that was parsed.
    pub outcomes: Vec<CommandOutcome>,
}

/// A single workspace.
#[derive(Debug)]
pub struct Workspace {
    /// The logical number of the workspace. Corresponds to the command to switch to this
    /// workspace. For named workspaces, this will be -1.
    pub num: i32,
    /// The name of this workspace (by default num+1), as changed by the user.
    pub name: String,
    /// Whether this workspace is currently visible on an output (multiple workspaces can be
    /// visible at the same time).
    pub visible: bool,
    /// Whether this workspace currently has the focus (only one workspace can have the focus
    /// at the same time).
    pub focused: bool,
    /// Whether a window on this workspace has the "urgent" flag set.
    pub urgent: bool,
    /// The rectangle of this workspace (equals the rect of the output it is on), consists of
    /// x, y, width, height.
    pub rect: (i32, i32, i32, i32),
    /// The video output this workspace is on (LVDS1, VGA1, …).
    pub output: String,
}

/// The reply to the `get_workspaces` request.
#[derive(Debug)]
pub struct Workspaces {
    /// A list of workspaces.
    pub workspaces: Vec<Workspace>,
}

/// The reply to the `subscribe` request.
#[derive(Debug)]
pub struct Subscribe {
    /// Indicates whether the subscription was successful (the default) or whether a JSON
    /// parse error occurred.
    pub success: bool,
}

/// A single output (display)
#[derive(Debug)]
pub struct Output {
    /// The name of this output (as seen in xrandr).
    pub name: String,
    /// Whether the output is currently active (has a valid mode).
    pub active: bool,
    /// Whether the output is currently the primary output.
    pub primary: bool,
    /// The name of the current workspace that is visible on this output. None if the output is
    /// not active.
    pub current_workspace: Option<String>,
    /// The rectangle of this output (equals the rect of the output it is on), consists of
    /// x, y, width, height.
    pub rect: (i32, i32, i32, i32),
}

/// The reply to the `get_outputs` request.
#[derive(Debug)]
pub struct Outputs {
    /// A list of outputs (displays)
    pub outputs: Vec<Output>,
}

#[derive(Eq, PartialEq, Debug, Hash)]
pub enum WindowProperty {
    Title,
    Instance,
    Class,
    WindowRole,
    TransientFor,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeType {
    Root,
    Output,
    Con,
    FloatingCon,
    Workspace,
    DockArea,
    /// A NodeType we don't support yet.
    Unknown,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeBorder {
    Normal,
    None,
    Pixel,
    /// A NodeBorder we don't support yet.
    Unknown,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeLayout {
    SplitH,
    SplitV,
    Stacked,
    Tabbed,
    DockArea,
    Output,
    /// A NodeLayout we don't support yet.
    Unknown,
}

/// The reply to the `get_tree` request.
#[derive(Debug, Clone)]
pub struct Node {
    /// List of child node IDs (see `nodes`, `floating_nodes` and `id`) in focus order. Traversing
    /// the tree by following the first entry in this array will result in eventually reaching the
    /// one node with `focused` set to true.
    pub focus: Vec<i64>,

    /// The child nodes of this container.
    pub nodes: Vec<Node>,

    /// The child floating nodes of this container.
    pub floating_nodes: Vec<Node>,

    /// The internal ID (actually a C pointer value) of this container. Do not make any
    /// assumptions about it. You can use it to (re-)identify and address containers when
    /// talking to i3.
    pub id: i64,

    /// The internal name of this container. For all containers which are part of the tree
    /// structure down to the workspace contents, this is set to a nice human-readable name of
    /// the container. For containers that have an X11 window, the content is the title
    /// (_NET_WM_NAME property) of that window. For all other containers, the content is not
    /// defined (yet).
    pub name: Option<String>,

    /// Type of this container. Can be one of "root", "output", "con", "floating_con",
    /// "workspace" or "dockarea".
    pub nodetype: NodeType,

    /// Can be either "normal", "none" or "1pixel", dependending on the container’s border
    /// style.
    pub border: NodeBorder,

    /// Number of pixels of the border width.
    pub current_border_width: i32,

    /// Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or "output". Other values
    /// might be possible in the future, should we add new layouts.
    pub layout: NodeLayout,

    /// The percentage which this container takes in its parent. A value of null means that the
    /// percent property does not make sense for this container, for example for the root
    /// container.
    pub percent: Option<f64>,

    /// The (x, y, width, height) absolute display coordinates for this container. Display
    /// coordinates means that when you have two 1600x1200 monitors on a single X11 Display
    /// (the standard way), the coordinates of the first window on the second monitor are
    /// (1600, 0, 1600, 1200).
    pub rect: (i32, i32, i32, i32),

    /// The (x, y, width, height) coordinates of the actual client window inside its container.
    /// These coordinates are  relative to the container and do not include the window
    /// decoration (which is actually rendered on the parent container). So for example, when
    /// using the default layout, you will have a 2 pixel border on each side, making the
    /// window_rect (2, 0, 632, 366).
    pub window_rect: (i32, i32, i32, i32),

    /// The (x, y, width, height) coordinates of the window decoration inside its container.
    /// These coordinates are relative to the container and do not include the actual client
    /// window.
    pub deco_rect: (i32, i32, i32, i32),

    /// The original geometry the window specified when i3 mapped it. Used when switching a
    /// window to floating mode, for example.
    pub geometry: (i32, i32, i32, i32),

    /// The X11 window ID of the actual client window inside this container. This field is set
    /// to null for split containers or otherwise empty containers. This ID corresponds to what
    /// xwininfo(1) and other X11-related tools display (usually in hex).
    pub window: Option<i32>,

    /// X11 window properties title, instance, class, window_role and transient_for.
    pub window_properties: Option<HashMap<WindowProperty, String>>,

    /// Whether this container (window, split container, floating container or workspace) has the
    /// urgency hint set, directly or indirectly. All parent containers up until the workspace
    /// container will be marked urgent if they have at least one urgent child.
    pub urgent: bool,

    /// Whether this container is currently focused.
    pub focused: bool,
}

/// The reply to the `get_marks` request.
///
/// Consists of a single vector of strings for each container that has a mark. A mark can only
/// be set on one container, so the vector is unique. The order of that vector is undefined. If
/// no window has a mark the response will be an empty vector.
#[derive(Debug)]
pub struct Marks {
    pub marks: Vec<String>,
}

/// The reply to the `get_bar_ids` request.
///
/// This can be used by third-party workspace bars (especially i3bar, but others are free to
/// implement compatible alternatives) to get the bar block configuration from i3.
#[derive(Debug)]
pub struct BarIds {
    /// A vector of configured bar IDs.
    pub ids: Vec<String>,
}

#[derive(Hash, Eq, PartialEq, Debug)]
pub enum ColorableBarPart {
    /// Background color of the bar.
    Background,

    /// Text color to be used for the statusline.
    Statusline,

    /// Text color to be used for the separator.
    Separator,

    /// Background color of the bar on the currently focused monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedBackground,

    /// Text color to be used for the statusline on the currently focused
    /// monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedStatusline,

    /// Text color to be used for the separator on the currently focused
    /// monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedSeparator,

    /// Text color for a workspace button when the workspace has focus.
    FocusedWorkspaceText,

    /// Background color for a workspace button when the workspace has focus.
    FocusedWorkspaceBg,

    /// Border color for a workspace button when the workspace has focus.
    FocusedWorkspaceBorder,

    /// Text color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceText,

    /// Background color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceBg,

    /// Border color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceBorder,

    /// Text color for a workspace button when the workspace does not have focus and is not
    /// active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceText,

    /// Background color for a workspace button when the workspace does not have focus and is
    /// not active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceBg,

    /// Border color for a workspace button when the workspace does not have focus and is
    /// not active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceBorder,

    /// Text color for workspaces which contain at least one window with the urgency hint set.
    UrgentWorkspaceText,

    /// Background color for workspaces which contain at least one window with the urgency hint
    /// set.
    UrgentWorkspaceBg,

    /// Border color for workspaces which contain at least one window with the urgency hint set.
    UrgentWorkspaceBorder,

    /// Text color for the binding mode indicator.
    BindingModeText,

    /// Background color for the binding mode indicator.
    BindingModeBg,

    /// Border color for the binding mode indicator.
    BindingModeBorder,

    /// A ColorableBarPart we don't support yet.
    Unknown,
}

/// The reply to the `get_bar_config` request.
///
/// This can be used by third-party workspace bars (especially i3bar, but others are free to
/// implement compatible alternatives) to get the bar block configuration from i3.
#[derive(Debug)]
pub struct BarConfig {
    /// The ID for this bar. Included in case you request multiple configurations and want to
    /// differentiate the different replies.
    pub id: String,

    /// Either dock (the bar sets the dock window type) or hide (the bar does not show unless a
    /// specific key is pressed).
    pub mode: String,

    /// Either bottom or top at the moment.
    pub position: String,

    /// Command which will be run to generate a statusline. Each line on stdout of this command
    /// will be displayed in the bar. At the moment, no formatting is supported.
    pub status_command: String,

    /// The font to use for text on the bar.
    pub font: String,

    /// Display workspace buttons or not? Defaults to true.
    pub workspace_buttons: bool,

    /// Display the mode indicator or not? Defaults to true.
    pub binding_mode_indicator: bool,

    /// Should the bar enable verbose output for debugging? Defaults to false.
    pub verbose: bool,

    /// Contains key/value pairs of colors. Each value is a color code in hex, formatted
    /// \#rrggbb (like in HTML).
    pub colors: HashMap<ColorableBarPart, String>,
}

/// The reply to the `get_version` request.
#[derive(Debug)]
pub struct Version {
    /// The major version of i3, such as 4.
    pub major: i32,

    /// The minor version of i3, such as 2. Changes in the IPC interface (new features) will
    /// only occur with new minor (or major) releases. However, bugfixes might be introduced in
    /// patch releases, too.
    pub minor: i32,

    /// The patch version of i3, such as 1 (when the complete version is 4.2.1). For versions
    /// such as 4.2, patch will be set to 0.
    pub patch: i32,

    /// A human-readable version of i3 containing the precise git version, build date and
    /// branch name. When you need to display the i3 version to your users, use the
    /// human-readable version whenever possible (since this is what i3 --version displays,
    /// too).
    pub human_readable: String,

    /// The current config path.
    pub loaded_config_file_name: String,
}

/// The reply to the `get_binding_modes` request.
#[cfg(feature = "i3-4-13")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-13")))]
#[derive(Debug)]
pub struct BindingModes {
    /// A vector of all currently configured binding modes.
    pub modes: Vec<String>,
}

/// The reply to the `get_config` request.
#[cfg(feature = "i3-4-14")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-14")))]
#[derive(Debug)]
pub struct Config {
    /// A string containing the config file as loaded by i3 most recently.
    pub config: String,
}
<commit_msg>Update reply.rs<commit_after>//! Abstractions for the replies passed back from i3.

use std::collections::HashMap;

/// The outcome of a single command.
#[derive(Debug)]
pub struct CommandOutcome {
    /// Whether the command was successful.
    pub success: bool,
    /// A human-readable error message.
    pub error: Option<String>,
}

/// The reply to the `command` request.
#[derive(Debug)]
pub struct Command {
    /// A list of `CommandOutcome` structs; one for each command that was parsed.
    pub outcomes: Vec<CommandOutcome>,
}

/// A single workspace.
#[derive(Debug)]
pub struct Workspace {
    /// The logical number of the workspace. Corresponds to the command to switch to this
    /// workspace. For named workspaces, this will be -1.
    pub num: i32,
    /// The name of this workspace (by default num+1), as changed by the user.
    pub name: String,
    /// Whether this workspace is currently visible on an output (multiple workspaces can be
    /// visible at the same time).
    pub visible: bool,
    /// Whether this workspace currently has the focus (only one workspace can have the focus
    /// at the same time).
    pub focused: bool,
    /// Whether a window on this workspace has the "urgent" flag set.
    pub urgent: bool,
    /// The rectangle of this workspace (equals the rect of the output it is on), consists of
    /// x, y, width, height.
    pub rect: (i32, i32, i32, i32),
    /// The video output this workspace is on (LVDS1, VGA1, …).
    pub output: String,
}

/// The reply to the `get_workspaces` request.
#[derive(Debug)]
pub struct Workspaces {
    /// A list of workspaces.
    pub workspaces: Vec<Workspace>,
}

/// The reply to the `subscribe` request.
#[derive(Debug)]
pub struct Subscribe {
    /// Indicates whether the subscription was successful (the default) or whether a JSON
    /// parse error occurred.
    pub success: bool,
}

/// A single output (display)
#[derive(Debug)]
pub struct Output {
    /// The name of this output (as seen in xrandr).
    pub name: String,
    /// Whether the output is currently active (has a valid mode).
    pub active: bool,
    /// Whether the output is currently the primary output.
    pub primary: bool,
    /// The name of the current workspace that is visible on this output. None if the output is
    /// not active.
    pub current_workspace: Option<String>,
    /// The rectangle of this output (equals the rect of the output it is on), consists of
    /// x, y, width, height.
    pub rect: (i32, i32, i32, i32),
}

/// The reply to the `get_outputs` request.
#[derive(Debug)]
pub struct Outputs {
    /// A list of outputs (displays)
    pub outputs: Vec<Output>,
}

#[derive(Eq, PartialEq, Debug, Hash, Clone)]
pub enum WindowProperty {
    Title,
    Instance,
    Class,
    WindowRole,
    TransientFor,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeType {
    Root,
    Output,
    Con,
    FloatingCon,
    Workspace,
    DockArea,
    /// A NodeType we don't support yet.
    Unknown,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeBorder {
    Normal,
    None,
    Pixel,
    /// A NodeBorder we don't support yet.
    Unknown,
}

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum NodeLayout {
    SplitH,
    SplitV,
    Stacked,
    Tabbed,
    DockArea,
    Output,
    /// A NodeLayout we don't support yet.
    Unknown,
}

/// The reply to the `get_tree` request.
#[derive(Debug, Clone)]
pub struct Node {
    /// List of child node IDs (see `nodes`, `floating_nodes` and `id`) in focus order. Traversing
    /// the tree by following the first entry in this array will result in eventually reaching the
    /// one node with `focused` set to true.
    pub focus: Vec<i64>,

    /// The child nodes of this container.
    pub nodes: Vec<Node>,

    /// The child floating nodes of this container.
    pub floating_nodes: Vec<Node>,

    /// The internal ID (actually a C pointer value) of this container. Do not make any
    /// assumptions about it. You can use it to (re-)identify and address containers when
    /// talking to i3.
    pub id: i64,

    /// The internal name of this container. For all containers which are part of the tree
    /// structure down to the workspace contents, this is set to a nice human-readable name of
    /// the container. For containers that have an X11 window, the content is the title
    /// (_NET_WM_NAME property) of that window. For all other containers, the content is not
    /// defined (yet).
    pub name: Option<String>,

    /// Type of this container. Can be one of "root", "output", "con", "floating_con",
    /// "workspace" or "dockarea".
    pub nodetype: NodeType,

    /// Can be either "normal", "none" or "1pixel", dependending on the container’s border
    /// style.
    pub border: NodeBorder,

    /// Number of pixels of the border width.
    pub current_border_width: i32,

    /// Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or "output". Other values
    /// might be possible in the future, should we add new layouts.
    pub layout: NodeLayout,

    /// The percentage which this container takes in its parent. A value of null means that the
    /// percent property does not make sense for this container, for example for the root
    /// container.
    pub percent: Option<f64>,

    /// The (x, y, width, height) absolute display coordinates for this container. Display
    /// coordinates means that when you have two 1600x1200 monitors on a single X11 Display
    /// (the standard way), the coordinates of the first window on the second monitor are
    /// (1600, 0, 1600, 1200).
    pub rect: (i32, i32, i32, i32),

    /// The (x, y, width, height) coordinates of the actual client window inside its container.
    /// These coordinates are  relative to the container and do not include the window
    /// decoration (which is actually rendered on the parent container). So for example, when
    /// using the default layout, you will have a 2 pixel border on each side, making the
    /// window_rect (2, 0, 632, 366).
    pub window_rect: (i32, i32, i32, i32),

    /// The (x, y, width, height) coordinates of the window decoration inside its container.
    /// These coordinates are relative to the container and do not include the actual client
    /// window.
    pub deco_rect: (i32, i32, i32, i32),

    /// The original geometry the window specified when i3 mapped it. Used when switching a
    /// window to floating mode, for example.
    pub geometry: (i32, i32, i32, i32),

    /// The X11 window ID of the actual client window inside this container. This field is set
    /// to null for split containers or otherwise empty containers. This ID corresponds to what
    /// xwininfo(1) and other X11-related tools display (usually in hex).
    pub window: Option<i32>,

    /// X11 window properties title, instance, class, window_role and transient_for.
    pub window_properties: Option<HashMap<WindowProperty, String>>,

    /// Whether this container (window, split container, floating container or workspace) has the
    /// urgency hint set, directly or indirectly. All parent containers up until the workspace
    /// container will be marked urgent if they have at least one urgent child.
    pub urgent: bool,

    /// Whether this container is currently focused.
    pub focused: bool,
}

/// The reply to the `get_marks` request.
///
/// Consists of a single vector of strings for each container that has a mark. A mark can only
/// be set on one container, so the vector is unique. The order of that vector is undefined. If
/// no window has a mark the response will be an empty vector.
#[derive(Debug)]
pub struct Marks {
    pub marks: Vec<String>,
}

/// The reply to the `get_bar_ids` request.
///
/// This can be used by third-party workspace bars (especially i3bar, but others are free to
/// implement compatible alternatives) to get the bar block configuration from i3.
#[derive(Debug)]
pub struct BarIds {
    /// A vector of configured bar IDs.
    pub ids: Vec<String>,
}

#[derive(Hash, Eq, PartialEq, Debug)]
pub enum ColorableBarPart {
    /// Background color of the bar.
    Background,

    /// Text color to be used for the statusline.
    Statusline,

    /// Text color to be used for the separator.
    Separator,

    /// Background color of the bar on the currently focused monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedBackground,

    /// Text color to be used for the statusline on the currently focused
    /// monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedStatusline,

    /// Text color to be used for the separator on the currently focused
    /// monitor output.
    #[cfg(feature = "i3-4-12")]
    #[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-12")))]
    FocusedSeparator,

    /// Text color for a workspace button when the workspace has focus.
    FocusedWorkspaceText,

    /// Background color for a workspace button when the workspace has focus.
    FocusedWorkspaceBg,

    /// Border color for a workspace button when the workspace has focus.
    FocusedWorkspaceBorder,

    /// Text color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceText,

    /// Background color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceBg,

    /// Border color for a workspace button when the workspace is active (visible) on some
    /// output, but the focus is on another one. You can only tell this apart from the
    /// focused workspace when you are using multiple monitors.
    ActiveWorkspaceBorder,

    /// Text color for a workspace button when the workspace does not have focus and is not
    /// active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceText,

    /// Background color for a workspace button when the workspace does not have focus and is
    /// not active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceBg,

    /// Border color for a workspace button when the workspace does not have focus and is
    /// not active (visible) on any output. This will be the case for most workspaces.
    InactiveWorkspaceBorder,

    /// Text color for workspaces which contain at least one window with the urgency hint set.
    UrgentWorkspaceText,

    /// Background color for workspaces which contain at least one window with the urgency hint
    /// set.
    UrgentWorkspaceBg,

    /// Border color for workspaces which contain at least one window with the urgency hint set.
    UrgentWorkspaceBorder,

    /// Text color for the binding mode indicator.
    BindingModeText,

    /// Background color for the binding mode indicator.
    BindingModeBg,

    /// Border color for the binding mode indicator.
    BindingModeBorder,

    /// A ColorableBarPart we don't support yet.
    Unknown,
}

/// The reply to the `get_bar_config` request.
///
/// This can be used by third-party workspace bars (especially i3bar, but others are free to
/// implement compatible alternatives) to get the bar block configuration from i3.
#[derive(Debug)]
pub struct BarConfig {
    /// The ID for this bar. Included in case you request multiple configurations and want to
    /// differentiate the different replies.
    pub id: String,

    /// Either dock (the bar sets the dock window type) or hide (the bar does not show unless a
    /// specific key is pressed).
    pub mode: String,

    /// Either bottom or top at the moment.
    pub position: String,

    /// Command which will be run to generate a statusline. Each line on stdout of this command
    /// will be displayed in the bar. At the moment, no formatting is supported.
    pub status_command: String,

    /// The font to use for text on the bar.
    pub font: String,

    /// Display workspace buttons or not? Defaults to true.
    pub workspace_buttons: bool,

    /// Display the mode indicator or not? Defaults to true.
    pub binding_mode_indicator: bool,

    /// Should the bar enable verbose output for debugging? Defaults to false.
    pub verbose: bool,

    /// Contains key/value pairs of colors. Each value is a color code in hex, formatted
    /// \#rrggbb (like in HTML).
    pub colors: HashMap<ColorableBarPart, String>,
}

/// The reply to the `get_version` request.
#[derive(Debug)]
pub struct Version {
    /// The major version of i3, such as 4.
    pub major: i32,

    /// The minor version of i3, such as 2. Changes in the IPC interface (new features) will
    /// only occur with new minor (or major) releases. However, bugfixes might be introduced in
    /// patch releases, too.
    pub minor: i32,

    /// The patch version of i3, such as 1 (when the complete version is 4.2.1). For versions
    /// such as 4.2, patch will be set to 0.
    pub patch: i32,

    /// A human-readable version of i3 containing the precise git version, build date and
    /// branch name. When you need to display the i3 version to your users, use the
    /// human-readable version whenever possible (since this is what i3 --version displays,
    /// too).
    pub human_readable: String,

    /// The current config path.
    pub loaded_config_file_name: String,
}

/// The reply to the `get_binding_modes` request.
#[cfg(feature = "i3-4-13")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-13")))]
#[derive(Debug)]
pub struct BindingModes {
    /// A vector of all currently configured binding modes.
    pub modes: Vec<String>,
}

/// The reply to the `get_config` request.
#[cfg(feature = "i3-4-14")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "i3-4-14")))]
#[derive(Debug)]
pub struct Config {
    /// A string containing the config file as loaded by i3 most recently.
    pub config: String,
}
<|endoftext|>