<commit_before>use super::err;

use std::io::{self, Write, Read};

use byteorder::{ReadBytesExt, WriteBytesExt};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// Enum containing all Types Numbers
pub enum Type
{
    Eoc              = 0,
    Boolean          = 1,
    Integer          = 2,
    BitString        = 3,
    OctetString      = 4,
    Null             = 5,
    ObjectIdentifier = 6,
    ObjectDescriptor = 7,
    External         = 8,
    Real             = 9,
    Enumerated       = 10,
    EmbeddedPdv      = 11,
    Utf8String       = 12,
    RelativeOid      = 13,
    Sequence         = 16,
    Set              = 17,
    NumericString    = 18,
    PrintableString  = 19,
    T61String        = 20,
    VideotexString   = 21,
    Ia5String        = 22,
    UtcTime          = 23,
    GeneralizedTime  = 24,
    GraphicString    = 25,
    VisibleString    = 26,
    GeneralString    = 27,
    UniversalString  = 28,
    CharacterString  = 29,
    BmpString        = 30,
    // TODO: Special Type for extended tag encoding?
}

impl Type
{
    pub fn from_u8(v: u8) -> Type
    {
        match v
        {
            0 =>  Type::Eoc,
            1 =>  Type::Boolean,
            2 =>  Type::Integer,
            3 =>  Type::BitString,
            4 =>  Type::OctetString,
            5 =>  Type::Null,
            6 =>  Type::ObjectIdentifier,
            7 =>  Type::ObjectDescriptor,
            8 =>  Type::External,
            9 =>  Type::Real,
            10 => Type::Enumerated,
            11 => Type::EmbeddedPdv,
            12 => Type::Utf8String,
            13 => Type::RelativeOid,
            16 => Type::Sequence,
            17 => Type::Set,
            18 => Type::NumericString,
            19 => Type::PrintableString,
            20 => Type::T61String,
            21 => Type::VideotexString,
            22 => Type::Ia5String,
            23 => Type::UtcTime,
            24 => Type::GeneralizedTime,
            25 => Type::GraphicString,
            26 => Type::VisibleString,
            27 => Type::GeneralString,
            28 => Type::UniversalString,
            29 => Type::CharacterString,
            30 => Type::BmpString,
            // BER uses 5 bits to encode the universal tags, and 31/0x1F/b11111 is used
            // to signal to use the long form of encoding
            // FIXME: This is a public function, better error handling!
            _  => unreachable!(),
        }
    }
}

#[derive(PartialEq, Eq, Debug)]
pub enum Class
{
    // TODO: Use BigInt instead of i64 to make encoding arbitrary sizes possbile?
    // TODO: Find out if even necessary for LDAP
    Universal(Type),
    Application(i64),
    ContextSpecific(i64),
    Private(i64),
}

/// ClassNumber contains the numerical representation of the tags class
#[derive(Debug, Copy, Clone)]
enum ClassNumber
{
    Universal       = 0,
    Application     = 1,
    ContextSpecific = 2,
    Private         = 3,
}

impl ClassNumber
{
    pub fn from_u8(v: u8) -> ClassNumber
    {
        match v
        {
            0 => ClassNumber::Universal,
            1 => ClassNumber::Application,
            2 => ClassNumber::ContextSpecific,
            3 => ClassNumber::Private,
            // FIXME: Better Error handling
            _ => unreachable!(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Structure
{
    Primitive   = 0,
    Constructed = 1,
}

impl Structure
{
    pub fn from_u8(v: u8) -> Structure
    {
        match v
        {
            0 => Structure::Primitive,
            1 => Structure::Constructed,
            // FIXME: Better Error handling
            _ => unreachable!(),
        }
    }
}

#[derive(PartialEq, Eq, Debug)]
pub enum Payload
{
    Primitive(Vec<u8>),
    Constructed(Vec<Tag>),
}

impl Payload
{
    /// Return the length of the PAYLOAD in bytes
    ///
    /// If the payload is Constructed, this function
    /// returns the lenght of all contained tags, not
    /// just their payload.
    pub fn len(&self) -> usize
    {
        let mut l: usize = 0;
        match *self
        {
            Payload::Primitive(ref v) => l = v.len() as usize,
            Payload::Constructed(ref v) =>
            {
                for tag in v
                {
                    l += tag.len();
                }
            }
        }

        return l;

    }
}

#[derive(PartialEq, Eq, Debug)]
pub struct Tag
{
    class: Class,
    payload: Payload,
    // Length can be constructed from the payload and the class
}

impl Tag
{
    pub fn read(r: &mut Read) -> Result<Tag, err::Error>
    {
        // First, read the type byte
        let tagbyte = try!(r.read_u8());

        // 0xC0 is 11000000 in binary
        let classnumber = ClassNumber::from_u8((tagbyte & 0xC0) >> 6);
        let structure = Structure::from_u8((tagbyte & 0x20) >> 5);

        let class: Class;

        if tagbyte & 0x1F == 0x1F
        {
            // Extended Tags
            class = match classnumber
            {
                ClassNumber::Universal => return Err(err::Error::new(err::Kind::InvalidLengthEncoding, None)),
                ClassNumber::Application => Class::Application(try!(Tag::read_extended_tags(r))),
                ClassNumber::ContextSpecific => Class::ContextSpecific(try!(Tag::read_extended_tags(r))),
                ClassNumber::Private => Class::Private(try!(Tag::read_extended_tags(r))),
            }
        }
        else
        {
            class = match classnumber
            {
                ClassNumber::Universal => Class::Universal(Type::from_u8(tagbyte & 0x1F)),
                ClassNumber::Application => Class::Application((tagbyte & 0x1F) as i64),
                ClassNumber::ContextSpecific => Class::ContextSpecific((tagbyte & 0x1F) as i64),
                ClassNumber::Private => Class::Private((tagbyte & 0x1F) as i64),
            };
        }

        // Next, read the lenght
        let lenghtbyte = try!(r.read_u8());
        let mut length: u64 = 0;

        if lenghtbyte == 0x80
        {
            // Indefinite lenght. NOPE. NOPENOPENOPE.
            return Err(err::Error::new(err::Kind::IndefiniteLength, None));
        }
        else if lenghtbyte & 0x80 == 0x80
        {
            // Long form
            let count = (lenghtbyte & 0x7F) as usize;

            for i in 0..count
            {
                let lenghtbyte = try!(r.read_u8());
                length |= (lenghtbyte as u64) << (count - i - 1 * 8);
            }
        }
        else
        {
            // Short form
            length = lenghtbyte as u64;
        }

        let payload: Payload;

        // At last, get the payload
        match structure
        {
            Structure::Primitive =>
            {
                let mut buffer = vec![0; length as usize];
                try!(r.read(&mut buffer));
                payload = Payload::Primitive(buffer);
            },
            Structure::Constructed =>
            {
                // Parse
                let mut tags = Vec::new();

                let mut left = length;

                while
                {
                    let tag = try!(Tag::read(r));
                    left -= tag.len();
                    tags.push(tag);

                    // If this returns false the while loop ends
                    left > 0
                } {}

                payload = Payload::Constructed(tags);
            },
        }

        Ok(Tag
           {
               class: class,
               payload: payload,
           })
    }

    fn read_extended_tags(mut r: &mut Read) -> Result<i64, err::Error>
    {
        let mut count = 0usize;
        let mut tag = 0i64;

        while count < 8 // i64, so max 8 iterations have space
        {
            let byte = try!(r.read_u8());

            // The first bit does not count towards the final ID
            let nbr = (byte & 0x7F) as i64;

            tag |= nbr << (7 * count);

            // If the 8th bit is not set this byte is the last
            if byte & 0x80 == 0
            {
                break;
            }
            count += 1;
        }

        Ok(tag)
    }

    pub fn write(&self, mut w: &mut Write) -> io::Result<()>
    {
        let class_number = match self.class
        {
            Class::Universal(_) => ClassNumber::Universal,
            Class::Application(_) => ClassNumber::Application,
            Class::ContextSpecific(_) => ClassNumber::ContextSpecific,
            Class::Private(_) => ClassNumber::Private,
        };

        let struct_number = match self.payload
        {
            Payload::Primitive(_) => Structure::Primitive,
            Payload::Constructed(_) => Structure::Constructed,
        };

        // Set to true if we have to write the tag in extended form
        let mut extended_tag = false;

        // Construct Type byte
        let type_byte =
            // First two bits: Class
            (class_number as u8) << 6 |
            // Bit 6: Primitive/Constructed
            (struct_number as u8) << 5 |
            // Bit 5-1: Tag Number
            match self.class
            {
                // tag will never be bigger than 30 so this is ok
                Class::Universal(tag) => (tag as u8),
                Class::Application(tag) | Class::ContextSpecific(tag) | Class::Private(tag) =>
                {
                    if tag > 30
                    {
                        // Write tags in extended form
                        extended_tag = true;
                        // This means we need to set the 5 tag bits to 11111, so 31 or 0x1F
                        31
                    }
                    else
                    {
                        (tag as u8)
                    }
                },
            }; // let type_byte

        // Write type byte
        try!(w.write_u8(type_byte));

        // Write the extended tag form
        if extended_tag
        {
            match self.class
            {
                Class::Universal(_) => unreachable!(),
                Class::Application(tag) | Class::ContextSpecific(tag) | Class::Private(tag) =>
                {
                    let mut tag = tag;
                    while tag > 0
                    {
                        let mut byte = (tag & 0x7F) as u8;

                        // Shift away the 7 bits we just took
                        tag >>= 7;
                        if tag != 0
                        {
                            // There are more bytes to go, so set the 8th bit
                            byte |= 0x80;
                        }

                        try!(w.write_u8(byte));
                    }
                },
            }
        }

        // Write length
        if self.payload.len() < 0x80
        {
            // Use the short form
            try!(w.write_u8(self.payload.len() as u8));
        }
        else
        {
            // Long form has to be used
            let mut count: u8 = 0;
            let mut len = self.payload.len();
            // For each byte of length increase the count by one
            while {count += 1; len >>= 8; len > 0 }{}

            let count = count;
            // Write the amount of length bytes that will follow
            try!(w.write_u8(count));

            for i in (0..count).rev()
            {
                // Write length bytes, most significant to least
                // FIXME: This assumes little-endianess on the CPU
                let byte = (
                    // Zero out everything except the byte we care about and then shift
                    // so only one byte is left
                    (self.payload.len() & (0xFF << i * 8)) >> i * 8
                ) as u8;

                // Write the length bytes sequentially
                try!(w.write_u8(byte))
            }
        }

        // Finally, write the payload
        match self.payload
        {
            Payload::Primitive(ref value) =>
            {
                try!(w.write_all(value))
            },
            Payload::Constructed(ref tags) =>
            {
                // Recurse into each tag and let it write itself
                for tag in tags
                {
                    try!(tag.write(w));
                }
            },
        }

        // Everything worked :D
        Ok(())
    } // write

    // FIXME: This function does not yet work!
    /// Returns the lenght of the whole tag in bytes
    pub fn len(&self) -> usize
    {
        let mut length: usize = 0;

        // Get the Lenght of the Class/PC/Type byte(s)
        length += match self.class
        {
            Class::Universal(_) => /* Universal is always exactly one byte */ 1,
            Class::Application(ref tag) | Class::ContextSpecific(ref tag) | Class::Private(ref tag) =>
            {
                // In case of the other three we actually have to look at their content
                2
            }
        };

        length += self.payload.len();

        length
    }
}

#[cfg(test)]
mod test
{
    use super::*;
    use std::io::Cursor;

    #[test]
    fn read_simple_tag()
    {
        let mut bytestream = Cursor::new(vec![2, 2, 255, 127]);
        let tag = Tag::read(&mut bytestream).unwrap();

        assert!(tag == Tag {
            class: Class::Universal(Type::Integer),
            length: 2u64,
            payload: Payload::Primitive(vec![255, 127]),
        });
    }

    #[test]
    fn write_simple_tag()
    {
        let payload = vec![255, 127];
        let tag = Tag
        {
            class: Class::Universal(Type::Integer),
            length: payload.len() as u64,
            payload: Payload::Primitive(payload.clone())
        };

        let mut buf = Vec::<u8>::new();
        tag.write(&mut buf).unwrap();
        assert!(buf == vec![0x2, 0x2, 0xFF, 0x7F]);
    }

    #[test]
    fn read_constructed_tag()
    {

    }

    #[test]
    fn write_constructed_tag()
    {
       // let payload = Payload::
        //     let tag = Tag
        //     {
        //         class: Class::Universal(Type::Sequence),
        //         length:
        //     }
    }
}
<commit_msg>Added length handling, constructed tags & tests<commit_after>use super::err;

use std::io::{self, Write, Read};

use byteorder::{ReadBytesExt, WriteBytesExt};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// Enum containing all Types Numbers
pub enum Type
{
    Eoc              = 0,
    Boolean          = 1,
    Integer          = 2,
    BitString        = 3,
    OctetString      = 4,
    Null             = 5,
    ObjectIdentifier = 6,
    ObjectDescriptor = 7,
    External         = 8,
    Real             = 9,
    Enumerated       = 10,
    EmbeddedPdv      = 11,
    Utf8String       = 12,
    RelativeOid      = 13,
    Sequence         = 16,
    Set              = 17,
    NumericString    = 18,
    PrintableString  = 19,
    T61String        = 20,
    VideotexString   = 21,
    Ia5String        = 22,
    UtcTime          = 23,
    GeneralizedTime  = 24,
    GraphicString    = 25,
    VisibleString    = 26,
    GeneralString    = 27,
    UniversalString  = 28,
    CharacterString  = 29,
    BmpString        = 30,
    // TODO: Special Type for extended tag encoding?
}

impl Type
{
    pub fn from_u8(v: u8) -> Type
    {
        match v
        {
            0 =>  Type::Eoc,
            1 =>  Type::Boolean,
            2 =>  Type::Integer,
            3 =>  Type::BitString,
            4 =>  Type::OctetString,
            5 =>  Type::Null,
            6 =>  Type::ObjectIdentifier,
            7 =>  Type::ObjectDescriptor,
            8 =>  Type::External,
            9 =>  Type::Real,
            10 => Type::Enumerated,
            11 => Type::EmbeddedPdv,
            12 => Type::Utf8String,
            13 => Type::RelativeOid,
            16 => Type::Sequence,
            17 => Type::Set,
            18 => Type::NumericString,
            19 => Type::PrintableString,
            20 => Type::T61String,
            21 => Type::VideotexString,
            22 => Type::Ia5String,
            23 => Type::UtcTime,
            24 => Type::GeneralizedTime,
            25 => Type::GraphicString,
            26 => Type::VisibleString,
            27 => Type::GeneralString,
            28 => Type::UniversalString,
            29 => Type::CharacterString,
            30 => Type::BmpString,
            // BER uses 5 bits to encode the universal tags, and 31/0x1F/b11111 is used
            // to signal to use the long form of encoding
            // FIXME: This is a public function, better error handling!
            _  => unreachable!(),
        }
    }
}

#[derive(PartialEq, Eq, Debug)]
pub enum Class
{
    // TODO: Use BigInt instead of i64 to make encoding arbitrary sizes possbile?
    // TODO: Find out if even necessary for LDAP
    Universal(Type),
    Application(i64),
    ContextSpecific(i64),
    Private(i64),
}

/// ClassNumber contains the numerical representation of the tags class
#[derive(Debug, Copy, Clone)]
enum ClassNumber
{
    Universal       = 0,
    Application     = 1,
    ContextSpecific = 2,
    Private         = 3,
}

impl ClassNumber
{
    pub fn from_u8(v: u8) -> ClassNumber
    {
        match v
        {
            0 => ClassNumber::Universal,
            1 => ClassNumber::Application,
            2 => ClassNumber::ContextSpecific,
            3 => ClassNumber::Private,
            // FIXME: Better Error handling
            _ => unreachable!(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Structure
{
    Primitive   = 0,
    Constructed = 1,
}

impl Structure
{
    pub fn from_u8(v: u8) -> Structure
    {
        match v
        {
            0 => Structure::Primitive,
            1 => Structure::Constructed,
            // FIXME: Better Error handling
            _ => unreachable!(),
        }
    }
}

#[derive(PartialEq, Eq, Debug)]
pub enum Payload
{
    Primitive(Vec<u8>),
    Constructed(Vec<Tag>),
}

impl Payload
{
    /// Return the length of the PAYLOAD in bytes
    ///
    /// If the payload is Constructed, this function
    /// returns the length of all contained tags, not
    /// just their payload.
    pub fn len(&self) -> usize
    {
        let mut l: usize = 0;
        match *self
        {
            Payload::Primitive(ref v) => l = v.len() as usize,
            Payload::Constructed(ref v) =>
            {
                for tag in v
                {
                    l += tag.len();
                }
            }
        }

        return l;

    }
}

#[derive(PartialEq, Eq, Debug)]
pub struct Tag
{
    class: Class,
    payload: Payload,
    // Length as encoded in the Tag -> Payload length, NOT TAG LENGTH
    length: u64,
}

impl Tag
{
    pub fn read(r: &mut Read) -> Result<Tag, err::Error>
    {
        // First, read the type byte
        let tagbyte = try!(r.read_u8());

        // 0xC0 is 11000000 in binary
        let classnumber = ClassNumber::from_u8((tagbyte & 0xC0) >> 6);
        let structure = Structure::from_u8((tagbyte & 0x20) >> 5);

        let class: Class;

        if tagbyte & 0x1F == 0x1F
        {
            // Extended Tags
            class = match classnumber
            {
                ClassNumber::Universal => return Err(err::Error::new(err::Kind::InvalidLengthEncoding, None)),
                ClassNumber::Application => Class::Application(try!(Tag::read_extended_tags(r))),
                ClassNumber::ContextSpecific => Class::ContextSpecific(try!(Tag::read_extended_tags(r))),
                ClassNumber::Private => Class::Private(try!(Tag::read_extended_tags(r))),
            }
        }
        else
        {
            class = match classnumber
            {
                ClassNumber::Universal => Class::Universal(Type::from_u8(tagbyte & 0x1F)),
                ClassNumber::Application => Class::Application((tagbyte & 0x1F) as i64),
                ClassNumber::ContextSpecific => Class::ContextSpecific((tagbyte & 0x1F) as i64),
                ClassNumber::Private => Class::Private((tagbyte & 0x1F) as i64),
            };
        }

        // Next, read the length
        let lengthbyte = try!(r.read_u8());
        let mut length: u64 = 0;

        if lengthbyte == 0x80
        {
            // Indefinite length. NOPE. NOPENOPENOPE.
            return Err(err::Error::new(err::Kind::IndefiniteLength, None));
        }
        else if lengthbyte & 0x80 == 0x80
        {
            // Long form
            let count = (lengthbyte & 0x7F) as usize;

            for i in 0..count
            {
                let lengthbyte = try!(r.read_u8());
                length |= (lengthbyte as u64) << (count - i - 1 * 8);
            }
        }
        else
        {
            // Short form
            length = lengthbyte as u64;
        }

        let payload: Payload;

        // At last, get the payload
        match structure
        {
            Structure::Primitive =>
            {
                let mut buffer = vec![0; length as usize];
                try!(r.read(&mut buffer));
                payload = Payload::Primitive(buffer);
            },
            Structure::Constructed =>
            {
                // Parse
                let mut tags = Vec::new();

                let mut left = length;

                while
                {
                    let tag = try!(Tag::read(r));
                    left -= tag.len() as u64;
                    tags.push(tag);

                    // If this returns false the while loop ends
                    left > 0
                } {}

                payload = Payload::Constructed(tags);
            },
        }

        Ok(Tag
           {
               class: class,
               payload: payload,
               length: length,
           })
    }

    fn read_extended_tags(mut r: &mut Read) -> Result<i64, err::Error>
    {
        let mut count = 0usize;
        let mut tag = 0i64;

        while count < 8 // i64, so max 8 iterations have space
        {
            let byte = try!(r.read_u8());

            // The first bit does not count towards the final ID
            let nbr = (byte & 0x7F) as i64;

            tag |= nbr << (7 * count);

            // If the 8th bit is not set this byte is the last
            if byte & 0x80 == 0
            {
                break;
            }
            count += 1;
        }

        Ok(tag)
    }

    pub fn write(&self, mut w: &mut Write) -> io::Result<()>
    {
        let class_number = match self.class
        {
            Class::Universal(_) => ClassNumber::Universal,
            Class::Application(_) => ClassNumber::Application,
            Class::ContextSpecific(_) => ClassNumber::ContextSpecific,
            Class::Private(_) => ClassNumber::Private,
        };

        let struct_number = match self.payload
        {
            Payload::Primitive(_) => Structure::Primitive,
            Payload::Constructed(_) => Structure::Constructed,
        };

        // Set to true if we have to write the tag in extended form
        let mut extended_tag = false;

        // Construct Type byte
        let type_byte =
            // First two bits: Class
            (class_number as u8) << 6 |
            // Bit 6: Primitive/Constructed
            (struct_number as u8) << 5 |
            // Bit 5-1: Tag Number
            match self.class
            {
                // tag will never be bigger than 30 so this is ok
                Class::Universal(tag) => (tag as u8),
                Class::Application(tag) | Class::ContextSpecific(tag) | Class::Private(tag) =>
                {
                    if tag > 30
                    {
                        // Write tags in extended form
                        extended_tag = true;
                        // This means we need to set the 5 tag bits to 11111, so 31 or 0x1F
                        31
                    }
                    else
                    {
                        (tag as u8)
                    }
                },
            }; // let type_byte

        // Write type byte
        try!(w.write_u8(type_byte));

        // Write the extended tag form
        if extended_tag
        {
            match self.class
            {
                Class::Universal(_) => unreachable!(),
                Class::Application(tag) | Class::ContextSpecific(tag) | Class::Private(tag) =>
                {
                    let mut tag = tag;
                    while tag > 0
                    {
                        let mut byte = (tag & 0x7F) as u8;

                        // Shift away the 7 bits we just took
                        tag >>= 7;
                        if tag != 0
                        {
                            // There are more bytes to go, so set the 8th bit
                            byte |= 0x80;
                        }

                        try!(w.write_u8(byte));
                    }
                },
            }
        }

        // Write length
        if self.length < 0x80
        {
            // Use the short form
            try!(w.write_u8(self.length as u8));
        }
        else
        {
            // Long form has to be used
            let mut count: u8 = 0;
            let mut len = self.payload.len();
            // For each byte of length increase the count by one
            while {count += 1; len >>= 8; len > 0 }{}

            let count = count;
            // Write the amount of length bytes that will follow
            try!(w.write_u8(count));

            for i in (0..count).rev()
            {
                // Write length bytes, most significant to least
                // FIXME: This assumes little-endianess on the CPU
                let byte = (
                    // Zero out everything except the byte we care about and then shift
                    // so only one byte is left
                    (self.payload.len() & (0xFF << i * 8)) >> i * 8
                ) as u8;

                // Write the length bytes sequentially
                try!(w.write_u8(byte))
            }
        }

        // Finally, write the payload
        match self.payload
        {
            Payload::Primitive(ref value) =>
            {
                try!(w.write_all(value))
            },
            Payload::Constructed(ref tags) =>
            {
                // Recurse into each tag and let it write itself
                for tag in tags
                {
                    try!(tag.write(w));
                }
            },
        }

        // Everything worked :D
        Ok(())
    } // write

    // FIXME: This function does not yet work!
    /// Returns the length of the whole tag in bytes
    pub fn len(&self) -> usize
    {
        let mut length: usize = 0;

        // Get the Lenght of the Class/PC/Type byte(s)
        length += match self.class
        {
            Class::Universal(_) => /* Universal is always exactly one byte */ 1,
            Class::Application(tag) | Class::ContextSpecific(tag) | Class::Private(tag) =>
            {
                // In case of the other three we actually have to look at their content
                let mut len = 0usize;
                let mut tag = tag;
                while tag < 0
                {
                    tag >>= 8;
                    len += 1;
                }
                len
            }
        };

        // Add space the length bytes take up
        if self.length < 127
        {
            // Short form was used -> Just one byte
            length += 1;
        }
        else
        {
            // Long form was used
            // Mask out first bit and add amount of length bytes that will follow
            length += (self.length & 0x80) as usize;
            // Add 1 for the first length byte
            length += 1;
        }

        // Add payload length
        length += self.payload.len();

        length
    }
}

#[cfg(test)]
mod test
{
    use super::*;
    use std::io::Cursor;

    #[test]
    fn read_simple_tag()
    {
        let mut bytestream = Cursor::new(vec![2, 2, 255, 127]);
        let tag = Tag::read(&mut bytestream).unwrap();

        assert!(tag == Tag {
            class: Class::Universal(Type::Integer),
            payload: Payload::Primitive(vec![255, 127]),
            length: 2,
        });
    }

    #[test]
    fn write_simple_tag()
    {
        let payload = vec![255, 127];
        let tag = Tag
        {
            class: Class::Universal(Type::Integer),
            payload: Payload::Primitive(payload.clone()),
            length: 2,
        };

        let mut buf = Vec::<u8>::new();
        tag.write(&mut buf).unwrap();
        assert!(buf == vec![0x2, 0x2, 0xFF, 0x7F]);
    }

    #[test]
    fn check_primitive_tag_length()
    {
        let content = "Hello World!".to_string();
        let tag = Tag
        {
            class: Class::Universal(Type::Utf8String),
            length: content.len() as u64,
            payload: Payload::Primitive(content.into_bytes()),
        };

        let mut buf = Vec::<u8>::new();
        tag.write(&mut buf).unwrap();

        assert!(tag.len() == 14);
    }

    #[test]
    fn read_constructed_tag()
    {
        let mut bytestream = Cursor::new(vec![48, 14, 12, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]);
        let tag = Tag::read(&mut bytestream).unwrap();

        assert!(tag == Tag
            {
                class: Class::Universal(Type::Sequence),
                length: 14u64,
                payload: Payload::Constructed(vec![
                    Tag
                    {
                        class: Class::Universal(Type::Utf8String),
                        length: 12u64,
                        payload: Payload::Primitive("Hello World!".to_string().into_bytes()),
                    }
                ]),
            })
    }

    #[test]
    fn write_constructed_tag()
    {
        let content = "Hello World!".to_string();
        let child = Tag
        {
            class: Class::Universal(Type::Utf8String),
            length: content.len() as u64,
            payload: Payload::Primitive(content.into_bytes()),
        };
        let parent = Tag
        {
            class: Class::Universal(Type::Sequence),
            length: child.len() as u64,
            payload: Payload::Constructed(vec![child]),
        };

        let mut buf = Vec::<u8>::new();
        parent.write(&mut buf).unwrap();

        assert!(buf == vec![48, 14, 12, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])
    }

    #[test]
    fn check_constructed_tag_length()
    {
        let content = "Hello World!".to_string();
        let child = Tag
        {
            class: Class::Universal(Type::Utf8String),
            length: content.len() as u64,
            payload: Payload::Primitive(content.into_bytes()),
        };
        let parent = Tag
        {
            class: Class::Universal(Type::Sequence),
            length: child.len() as u64,
            payload: Payload::Constructed(vec![child]),
        };

        assert!(parent.len() == 16);
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate extra;

use std::cell::Cell; // Provide mutable fields in immutable structs
use std::env;
use std::error::Error;
use std::fs::{self, File};
use std::io::{self, BufReader, Read, Stderr, StdinLock, StdoutLock, Write};
use std::process::exit;
use extra::option::OptionalExt;

const MAN_PAGE: &'static str = /* @MANSTART{cat} */ r#"NAME
    cat - concatenate files and print on the standard output

SYNOPSIS
    cat [-h | --help] [-A | --show-all] [-b | --number-nonblank] [-e] [-E | --show-ends]
        [-n | --number] [-s | --squeeze-blank] [-t] [-T] FILES...

DESCRIPTION
    Concatenates all files to the standard output.

    If no file is given, or if FILE is '-', read from standard input.

OPTIONS
    -A
    --show-all
        equivalent to -v -E -T

    -b
    --number-nonblank
        number nonempty output lines, overriding -n

    -e
        equivalent to -v -E

    -E
    --show-ends
        display $ at the end of each line

    -n
    --number
        number all output lines

    -s
    --squeeze-blank
        supress repeated empty output lines

    -t
        equivalent to -v -T

    -T
    --show_tabs
        display TAB characters as ^I

    -v
    --show-nonprinting
        use caret (^) and M- notation, except for LFD and TAB.

    -h
    --help
        display this help and exit

AUTHOR
    Written by Michael Murphy.
"#; /* @MANEND */

struct Program {
    exit_status:      Cell<i32>,
    number:           bool,
    number_nonblank:  bool,
    show_ends:        bool,
    show_tabs:        bool,
    show_nonprinting: bool,
    squeeze_blank:    bool,
    paths:            Vec<String>,
}

impl Program {
    /// Initialize the program's arguments and flags.
    fn initialize(stdout: &mut StdoutLock, stderr: &mut Stderr) -> Program {
        let mut cat = Program {
            exit_status:      Cell::new(0i32),
            number:           false,
            number_nonblank:  false,
            show_ends:        false,
            show_tabs:        false,
            show_nonprinting: false,
            squeeze_blank:    false,
            paths:            Vec::new()
        };
        for arg in env::args().skip(1) {
            if arg.starts_with('-') && &arg != "-" {
                match arg.as_str() {
                    "-h" | "--help" => {
                        stdout.write(MAN_PAGE.as_bytes()).try(stderr);
                        stdout.flush().try(stderr);
                        exit(0);
                    }
                    "-A" | "--show-all" => {
                        cat.show_nonprinting = true;
                        cat.show_ends = true;
                        cat.show_tabs = true;
                    },
                    "-b" | "--number-nonblank" => {
                        cat.number_nonblank = true;
                        cat.number = false;
                    },
                    "-e" => {
                        cat.show_nonprinting = true;
                        cat.show_ends = true;
                    },
                    "-E" | "--show-ends" => cat.show_ends = true,
                    "-n" | "--number" => {
                        cat.number = true;
                        cat.number_nonblank = false;
                    },
                    "-s" | "--squeeze-blank" => cat.squeeze_blank = true,
                    "-t" => {
                        cat.show_nonprinting = true;
                        cat.show_tabs = true;
                    },
                    "-T" => cat.show_tabs = true,
                    "-v" | "--show-nonprinting" => {
                        cat.show_nonprinting = true;
                    },
                    _ => {
                        stderr.write(b"invalid option -- '").try(stderr);
                        stderr.write(arg.as_bytes()).try(stderr);
                        stderr.write(b"'\nTry 'cat --help' for more information.\n").try(stderr);
                        stderr.flush().try(stderr);
                        exit(1);
                    }
                }
            } else {
                cat.paths.push(arg);
            }
        }
        cat
    }

    /// Execute the parameters given to the program.
    fn and_execute(&self, stdout: &mut StdoutLock, stderr: &mut Stderr) -> i32 {
        let stdin = io::stdin();
        let stdin = &mut stdin.lock();
        let line_count = &mut 0usize;
        let flags_enabled = self.number || self.number_nonblank || self.show_ends || self.show_tabs ||
                            self.squeeze_blank || self.show_nonprinting;

        if self.paths.is_empty() && flags_enabled {
            self.cat_stdin(line_count, stdin, stdout, stderr);
        } else if self.paths.is_empty() {
            io::copy(stdin, stdout).try(stderr);
        } else {
            for path in &self.paths {
                if flags_enabled && path == "-" {
                    self.cat_stdin(line_count, stdin, stdout, stderr);
                } else if flags_enabled {
                    let file = match fs::File::open(&path) {
                        Ok(file) => file,
                        Err(message) => {
                            stderr.write(&path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);;
                            continue
                        }
                    };

                    self.cat_file(file, line_count, stdout, stderr);
                } else if path == "-" {
                    io::copy(stdin, stdout).try(stderr);
                } else {
                    let file = &mut match fs::File::open(&path) {
                        Ok(file) => file,
                        Err(message) => {
                            stderr.write(&path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);;
                            continue
                        }
                    };

                    io::copy(file, stdout).try(stderr);
                }
            }
        }
        self.exit_status.get()
    }

    /// Cats a file based on the flag arguments given to the program.
    fn cat_file(&self, file: File, line_count: &mut usize, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in BufReader::new(file).bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }

    /// Cats stdin based on the flag arguments given to the program.
    fn cat_stdin(&self, line_count: &mut usize, stdin: &mut StdinLock, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in stdin.bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }
}

/// Increase the character count by one if number printing is enabled.
fn count_character(character_count: &mut usize, number: &bool, number_nonblank: &bool) {
    if *number || *number_nonblank {
        *character_count += 1;
    }
}

/// Print a caret notation to stdout.
fn push_caret(stdout: &mut StdoutLock, stderr: &mut Stderr, notation: u8) {
    stdout.write(&[b'^']).try(stderr);
    stdout.write(&[notation]).try(stderr);
}

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    exit(Program::initialize(&mut stdout, &mut stderr).and_execute(&mut stdout, &mut stderr));
}
<commit_msg>Better Argument Parsing for Cat Supports writing arguments as -vET rather than -v -E -T.<commit_after>#![deny(warnings)]

extern crate extra;

use std::cell::Cell; // Provide mutable fields in immutable structs
use std::env;
use std::error::Error;
use std::fs::{self, File};
use std::io::{self, BufReader, Read, Stderr, StdinLock, StdoutLock, Write};
use std::process::exit;
use extra::option::OptionalExt;

const MAN_PAGE: &'static str = /* @MANSTART{cat} */ r#"NAME
    cat - concatenate files and print on the standard output

SYNOPSIS
    cat [-h | --help] [-A | --show-all] [-b | --number-nonblank] [-e] [-E | --show-ends]
        [-n | --number] [-s | --squeeze-blank] [-t] [-T] FILES...

DESCRIPTION
    Concatenates all files to the standard output.

    If no file is given, or if FILE is '-', read from standard input.

OPTIONS
    -A
    --show-all
        equivalent to -vET

    -b
    --number-nonblank
        number nonempty output lines, overriding -n

    -e
        equivalent to -vE

    -E
    --show-ends
        display $ at the end of each line

    -n
    --number
        number all output lines

    -s
    --squeeze-blank
        supress repeated empty output lines

    -t
        equivalent to -vT

    -T
    --show_tabs
        display TAB characters as ^I

    -v
    --show-nonprinting
        use caret (^) and M- notation, except for LFD and TAB.

    -h
    --help
        display this help and exit

AUTHOR
    Written by Michael Murphy.
"#; /* @MANEND */

struct Program {
    exit_status:      Cell<i32>,
    number:           bool,
    number_nonblank:  bool,
    show_ends:        bool,
    show_tabs:        bool,
    show_nonprinting: bool,
    squeeze_blank:    bool,
    paths:            Vec<String>,
}

impl Program {
    /// Initialize the program's arguments and flags.
    fn initialize(stdout: &mut StdoutLock, stderr: &mut Stderr) -> Program {
        let mut cat = Program {
            exit_status:      Cell::new(0i32),
            number:           false,
            number_nonblank:  false,
            show_ends:        false,
            show_tabs:        false,
            show_nonprinting: false,
            squeeze_blank:    false,
            paths:            Vec::new()
        };
        for argument in env::args().skip(1) {
            if argument.starts_with('-') && &argument != "-" {
                if argument.starts_with("--") {
                    match argument.as_str() {
                        "--help" => {
                            stdout.write(MAN_PAGE.as_bytes()).try(stderr);
                            stdout.flush().try(stderr);
                            exit(0);
                        }
                        "--show-all" => {
                            cat.show_nonprinting = true;
                            cat.show_ends = true;
                            cat.show_tabs = true;
                        },
                        "--number-nonblank" => {
                            cat.number_nonblank = true;
                            cat.number = false;
                        },
                        "--show-ends" => cat.show_ends = true,
                        "--number" => {
                            cat.number = true;
                            cat.number_nonblank = false;
                        },
                        "--squeeze-blank" => cat.squeeze_blank = true,
                        "--show-tabs" => cat.show_tabs = true,
                        "--show-nonprinting" => {
                            cat.show_nonprinting = true;
                        },
                        _ => {
                            stderr.write(b"invalid option -- '").try(stderr);
                            stderr.write(argument.as_bytes()).try(stderr);
                            stderr.write(b"'\nTry 'cat --help' for more information.\n").try(stderr);
                            stderr.flush().try(stderr);
                            exit(1);
                        }
                    }
                } else {
                    for character in argument.bytes().skip(1) {
                        match character {
                            b'h' => {
                                stdout.write(MAN_PAGE.as_bytes()).try(stderr);
                                stdout.flush().try(stderr);
                                exit(0);
                            }
                            b'A' => {
                                cat.show_nonprinting = true;
                                cat.show_ends = true;
                                cat.show_tabs = true;
                            },
                            b'b'=> {
                                cat.number_nonblank = true;
                                cat.number = false;
                            },
                            b'e' => {
                                cat.show_nonprinting = true;
                                cat.show_ends = true;
                            },
                            b'E'=> cat.show_ends = true,
                            b'n' => {
                                cat.number = true;
                                cat.number_nonblank = false;
                            },
                            b's' => cat.squeeze_blank = true,
                            b't' => {
                                cat.show_nonprinting = true;
                                cat.show_tabs = true;
                            },
                            b'T' => cat.show_tabs = true,
                            b'v' => {
                                cat.show_nonprinting = true;
                            },
                            _ => {
                                stderr.write(b"invalid option -- '").try(stderr);
                                stderr.write(&[character]).try(stderr);
                                stderr.write(b"'\nTry 'cat --help' for more information.\n").try(stderr);
                                stderr.flush().try(stderr);
                                exit(1);
                            }
                        }
                    }
                }
            } else {
                cat.paths.push(argument);
            }
        }
        cat
    }

    /// Execute the parameters given to the program.
    fn and_execute(&self, stdout: &mut StdoutLock, stderr: &mut Stderr) -> i32 {
        let stdin = io::stdin();
        let stdin = &mut stdin.lock();
        let line_count = &mut 0usize;
        let flags_enabled = self.number || self.number_nonblank || self.show_ends || self.show_tabs ||
                            self.squeeze_blank || self.show_nonprinting;

        if self.paths.is_empty() && flags_enabled {
            self.cat_stdin(line_count, stdin, stdout, stderr);
        } else if self.paths.is_empty() {
            io::copy(stdin, stdout).try(stderr);
        } else {
            for path in &self.paths {
                if flags_enabled && path == "-" {
                    self.cat_stdin(line_count, stdin, stdout, stderr);
                } else if flags_enabled {
                    let file = match fs::File::open(&path) {
                        Ok(file) => file,
                        Err(message) => {
                            stderr.write(&path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);;
                            continue
                        }
                    };

                    self.cat_file(file, line_count, stdout, stderr);
                } else if path == "-" {
                    io::copy(stdin, stdout).try(stderr);
                } else {
                    let file = &mut match fs::File::open(&path) {
                        Ok(file) => file,
                        Err(message) => {
                            stderr.write(&path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);;
                            continue
                        }
                    };

                    io::copy(file, stdout).try(stderr);
                }
            }
        }
        self.exit_status.get()
    }

    /// Cats a file based on the flag arguments given to the program.
    fn cat_file(&self, file: File, line_count: &mut usize, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in BufReader::new(file).bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }

    /// Cats stdin based on the flag arguments given to the program.
    fn cat_stdin(&self, line_count: &mut usize, stdin: &mut StdinLock, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in stdin.bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }
}

/// Increase the character count by one if number printing is enabled.
fn count_character(character_count: &mut usize, number: &bool, number_nonblank: &bool) {
    if *number || *number_nonblank {
        *character_count += 1;
    }
}

/// Print a caret notation to stdout.
fn push_caret(stdout: &mut StdoutLock, stderr: &mut Stderr, notation: u8) {
    stdout.write(&[b'^']).try(stderr);
    stdout.write(&[notation]).try(stderr);
}

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    exit(Program::initialize(&mut stdout, &mut stderr).and_execute(&mut stdout, &mut stderr));
}
<|endoftext|>


<commit_before>use std::num::Wrapping;
use basetypes::*;

// We use "u64" bit-sets called bitboards (BB) to represent a set of
// squares on the board. Here are some useful square-sets.

/// Empty bitboard
pub const EMPTY_SET: u64 = 0;

/// Completely full bitboard
pub const UNIVERSAL_SET: u64 = 0xffffffffffffffff;

pub const BB_RANK_1: u64 = 0b11111111;
pub const BB_RANK_2: u64 = BB_RANK_1 << 8;
pub const BB_RANK_3: u64 = BB_RANK_2 << 8;
pub const BB_RANK_4: u64 = BB_RANK_3 << 8;
pub const BB_RANK_5: u64 = BB_RANK_4 << 8;
pub const BB_RANK_6: u64 = BB_RANK_5 << 8;
pub const BB_RANK_7: u64 = BB_RANK_6 << 8;
pub const BB_RANK_8: u64 = BB_RANK_7 << 8;

pub const BB_FILE_A: u64 = 1 << A1 | 1 << A2 | 1 << A3 | 1 << A4 | 1 << A5 | 1 << A6 | 1 << A7 |
                           1 << A8;
pub const BB_FILE_B: u64 = BB_FILE_A << 1;
pub const BB_FILE_C: u64 = BB_FILE_B << 1;
pub const BB_FILE_D: u64 = BB_FILE_C << 1;
pub const BB_FILE_E: u64 = BB_FILE_D << 1;
pub const BB_FILE_F: u64 = BB_FILE_E << 1;
pub const BB_FILE_G: u64 = BB_FILE_F << 1;
pub const BB_FILE_H: u64 = BB_FILE_G << 1;

pub const PAWN_PROMOTION_RANKS: u64 = BB_RANK_1 | BB_RANK_8;

#[inline(always)]
pub fn ls1b(x: u64) -> u64 {
    (x as i64 & (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

#[inline(always)]
pub fn reset_ls1b(x: &mut u64) {
    *x &= (Wrapping(*x) - Wrapping(1)).0;
}

#[inline(always)]
pub fn above_ls1b_mask(x: u64) -> u64 {
    assert!(x != 0);
    (x as i64 ^ (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

#[inline(always)]
pub fn below_lsb1_mask_including(x: u64) -> u64 {
    assert!(x != 0);
    x ^ (x - 1)
}

#[inline(always)]
pub fn below_lsb1_mask(x: u64) -> u64 {
    assert!(x != 0);
    !x & (x - 1)
}

#[inline(always)]
pub fn smeared_ls1b_up(x: u64) -> u64 {
    assert!(x != 0);
    ((x as i64) | (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

#[inline(always)]
pub fn smeared_ls1b_down(x: u64) -> u64 {
    assert!(x != 0);
    x | (x - 1)
}

#[inline(always)]
pub fn gen_shift(x: u64, s: isize) -> u64 {
    if s > 0 {
        x << s
    } else {
        x >> -s
    }
}

#[inline(always)]
pub fn bitscan_forward(b: u64) -> Square {
    assert!(b != 0);
    bitscan_1bit(ls1b(b))
}

#[inline(always)]
pub fn bitscan_forward_and_reset(b: &mut u64) -> Square {
    assert!(*b != 0);
    let ls1b = ls1b(*b);
    *b ^= ls1b;
    bitscan_1bit(ls1b)
}

#[inline(always)]
pub fn bitscan_1bit(b: u64) -> Square {
    assert!(b != 0);
    assert_eq!(b, ls1b(b));
    const DEBRUIJN64: Wrapping<u64> = Wrapping(0x03f79d71b4cb0a89);
    const INDEX64: [Square; 64] = [0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62,
                                   55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5, 63,
                                   47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11, 46,
                                   26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6];
    unsafe { *INDEX64.get_unchecked(((Wrapping(b) * DEBRUIJN64).0 >> 58) as usize) }
}

#[inline]
pub fn pop_count(mut b: u64) -> usize {
   let mut count = 0;
   while b != 0 {
       count += 1;
       reset_ls1b(&mut b);
   }
   count
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ls1b_functions() {
        assert_eq!(ls1b(0x100100), 0x100);
        assert_eq!(ls1b(0x8000000000000000), 0x8000000000000000);
        assert_eq!(ls1b(0xf800000000000000), 0x0800000000000000);
        assert_eq!(ls1b(0), 0);
        let mut x = 0x100100u64;
        reset_ls1b(&mut x);
        assert_eq!(x, 0x100000);
        reset_ls1b(&mut x);
        assert_eq!(x, 0);
        reset_ls1b(&mut x);
        assert_eq!(x, 0);
        assert_eq!(above_ls1b_mask(0b11101000), 0xfffffffffffffff0);
        assert_eq!(above_ls1b_mask(0x8000000000000000), 0);
        assert_eq!(below_lsb1_mask_including(0b11101000), 0b1111);
        assert_eq!(below_lsb1_mask(0b11101000), 0b111);
        assert_eq!(smeared_ls1b_up(0b1010000), 0xfffffffffffffff0);
        assert_eq!(smeared_ls1b_up(0x8000000000000000), 0x8000000000000000);
        assert_eq!(smeared_ls1b_down(0b1010000), 0b1011111);
        assert_eq!(pop_count(0), 0);
        assert_eq!(pop_count(0b1001101), 4);
        assert_eq!(pop_count(0xffffffffffffffff), 64);
    }

    #[test]
    fn test_bitscan() {
        assert_eq!(bitscan_forward(0b1001101), 0);
        assert_eq!(bitscan_forward(0b1001000), 3);
        assert_eq!(bitscan_forward(0xf000000000000000), 60);
        let mut b = 0b1100100;
        assert_eq!(bitscan_forward_and_reset(&mut b), 2);
        assert_eq!(b, 0b1100000);
        assert_eq!(bitscan_1bit(0b1000), bitscan_forward(0b1000));
    }

}
<commit_msg>docs on bitsets.rs<commit_after>use std::num::Wrapping;
use basetypes::*;

// We use "u64" bit-sets called bitboards (BB) to represent a set of
// squares on the board. Here are some useful square-sets.

/// Empty bitboard
pub const EMPTY_SET: u64 = 0;

/// Completely full bitboard
pub const UNIVERSAL_SET: u64 = 0xffffffffffffffff;

pub const BB_RANK_1: u64 = 0b11111111;
pub const BB_RANK_2: u64 = BB_RANK_1 << 8;
pub const BB_RANK_3: u64 = BB_RANK_2 << 8;
pub const BB_RANK_4: u64 = BB_RANK_3 << 8;
pub const BB_RANK_5: u64 = BB_RANK_4 << 8;
pub const BB_RANK_6: u64 = BB_RANK_5 << 8;
pub const BB_RANK_7: u64 = BB_RANK_6 << 8;
pub const BB_RANK_8: u64 = BB_RANK_7 << 8;

pub const BB_FILE_A: u64 = 1 << A1 | 1 << A2 | 1 << A3 | 1 << A4 | 1 << A5 | 1 << A6 | 1 << A7 |
                           1 << A8;
pub const BB_FILE_B: u64 = BB_FILE_A << 1;
pub const BB_FILE_C: u64 = BB_FILE_B << 1;
pub const BB_FILE_D: u64 = BB_FILE_C << 1;
pub const BB_FILE_E: u64 = BB_FILE_D << 1;
pub const BB_FILE_F: u64 = BB_FILE_E << 1;
pub const BB_FILE_G: u64 = BB_FILE_F << 1;
pub const BB_FILE_H: u64 = BB_FILE_G << 1;

pub const PAWN_PROMOTION_RANKS: u64 = BB_RANK_1 | BB_RANK_8;

/// Returns the least significant bit of a value.
///
/// The way to calculate this is: `ls1b(x) = x & -x;`. If `x` is `0`
/// this function returns `0`.
///
/// # Examples
///
/// ```text
///
///        x         &        -x         =      ls1b(x)
/// . . . . . . . .     1 1 1 1 1 1 1 1     . . . . . . . .
/// . . 1 . 1 . . .     1 1 . 1 . 1 1 1     . . . . . . . .
/// . 1 . . . 1 . .     1 . 1 1 1 . 1 1     . . . . . . . .
/// . . . . . . . .     1 1 1 1 1 1 1 1     . . . . . . . .
/// . 1 . . . 1 . .  &  1 . 1 1 1 . 1 1  =  . . . . . . . .
/// . . 1 . 1 . . .     . . 1 1 . 1 1 1     . . 1 . . . . .
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// ```
#[inline(always)]
pub fn ls1b(x: u64) -> u64 {
    (x as i64 & (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

/// Sets the least significant bit of a value to zero.
#[inline(always)]
pub fn reset_ls1b(x: &mut u64) {
    *x &= (Wrapping(*x) - Wrapping(1)).0;
}

/// Returns a mask with all bits above the LS1B set to 1.
///
/// The way to calculate this is: `above_ls1b_mask(x) = x ^ -x;`.
///
/// If `x` is `0` this function returns `0`.
///
/// # Examples:
///
/// ```text
///
///       x          ^        -x         =  above_LS1B_mask(x)
/// . . . . . . . .     1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1
/// . . 1 . 1 . . .     1 1 . 1 . 1 1 1     1 1 1 1 1 1 1 1
/// . 1 . . . 1 . .     1 . 1 1 1 . 1 1     1 1 1 1 1 1 1 1
/// . . . . . . . .     1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1
/// . 1 . . . 1 . .  ^  1 . 1 1 1 . 1 1  =  1 1 1 1 1 1 1 1
/// . . 1 . 1 . . .     . . 1 1 . 1 1 1     . . . 1 1 1 1 1
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// ```
#[inline(always)]
pub fn above_ls1b_mask(x: u64) -> u64 {
    assert!(x != 0);
    (x as i64 ^ (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

/// Returns a mask with all bits below and including the LS1B set to
/// 1.
///
/// The way to calculate this is: `below_lsb1_mask_including(x) = x ^ (x - 1);`.
///
/// If `x` is `0` this function returns `0xffffffffffffffff`.
///
/// # Examples:
///
/// ```text
///       x          ^      (x - 1)      =  below_lsb1_mask_including(x)
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// . . 1 . 1 . . .     . . 1 . 1 . . .     . . . . . . . .
/// . 1 . . . 1 . .     . 1 . . . 1 . .     . . . . . . . .
/// . . . . . . . .     . . . . . . . .     . . . . . . . .
/// . 1 . . . 1 . .  ^  . 1 . . . 1 . .  =  . . . . . . . .
/// . . 1 . 1 . . .     1 1 . . 1 . . .     1 1 1 . . . . .
/// . . . . . . . .     1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1
/// . . . . . . . .     1 1 1 1 1 1 1 1     1 1 1 1 1 1 1 1
/// ```
#[inline(always)]
pub fn below_lsb1_mask_including(x: u64) -> u64 {
    assert!(x != 0);
    x ^ (x - 1)
}

#[inline(always)]
pub fn below_lsb1_mask(x: u64) -> u64 {
    assert!(x != 0);
    !x & (x - 1)
}

#[inline(always)]
pub fn smeared_ls1b_up(x: u64) -> u64 {
    assert!(x != 0);
    ((x as i64) | (Wrapping(0) - Wrapping(x as i64)).0) as u64
}

#[inline(always)]
pub fn smeared_ls1b_down(x: u64) -> u64 {
    assert!(x != 0);
    x | (x - 1)
}

#[inline(always)]
pub fn gen_shift(x: u64, s: isize) -> u64 {
    if s > 0 {
        x << s
    } else {
        x >> -s
    }
}

#[inline(always)]
pub fn bitscan_forward(b: u64) -> Square {
    assert!(b != 0);
    bitscan_1bit(ls1b(b))
}

#[inline(always)]
pub fn bitscan_forward_and_reset(b: &mut u64) -> Square {
    assert!(*b != 0);
    let ls1b = ls1b(*b);
    *b ^= ls1b;
    bitscan_1bit(ls1b)
}

#[inline(always)]
pub fn bitscan_1bit(b: u64) -> Square {
    assert!(b != 0);
    assert_eq!(b, ls1b(b));
    const DEBRUIJN64: Wrapping<u64> = Wrapping(0x03f79d71b4cb0a89);
    const INDEX64: [Square; 64] = [0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62,
                                   55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5, 63,
                                   47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11, 46,
                                   26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6];
    unsafe { *INDEX64.get_unchecked(((Wrapping(b) * DEBRUIJN64).0 >> 58) as usize) }
}

#[inline]
pub fn pop_count(mut b: u64) -> usize {
   let mut count = 0;
   while b != 0 {
       count += 1;
       reset_ls1b(&mut b);
   }
   count
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ls1b_functions() {
        assert_eq!(ls1b(0x100100), 0x100);
        assert_eq!(ls1b(0x8000000000000000), 0x8000000000000000);
        assert_eq!(ls1b(0xf800000000000000), 0x0800000000000000);
        assert_eq!(ls1b(0), 0);
        let mut x = 0x100100u64;
        reset_ls1b(&mut x);
        assert_eq!(x, 0x100000);
        reset_ls1b(&mut x);
        assert_eq!(x, 0);
        reset_ls1b(&mut x);
        assert_eq!(x, 0);
        assert_eq!(above_ls1b_mask(0b11101000), 0xfffffffffffffff0);
        assert_eq!(above_ls1b_mask(0x8000000000000000), 0);
        assert_eq!(below_lsb1_mask_including(0b11101000), 0b1111);
        assert_eq!(below_lsb1_mask(0b11101000), 0b111);
        assert_eq!(smeared_ls1b_up(0b1010000), 0xfffffffffffffff0);
        assert_eq!(smeared_ls1b_up(0x8000000000000000), 0x8000000000000000);
        assert_eq!(smeared_ls1b_down(0b1010000), 0b1011111);
        assert_eq!(pop_count(0), 0);
        assert_eq!(pop_count(0b1001101), 4);
        assert_eq!(pop_count(0xffffffffffffffff), 64);
    }

    #[test]
    fn test_bitscan() {
        assert_eq!(bitscan_forward(0b1001101), 0);
        assert_eq!(bitscan_forward(0b1001000), 3);
        assert_eq!(bitscan_forward(0xf000000000000000), 60);
        let mut b = 0b1100100;
        assert_eq!(bitscan_forward_and_reset(&mut b), 2);
        assert_eq!(b, 0b1100000);
        assert_eq!(bitscan_1bit(0b1000), bitscan_forward(0b1000));
    }

}
<|endoftext|>


<commit_before>use cgmath;
use cgmath::{Matrix4, Point3, Vector3};
use graphics::camera::CameraInputState;
use shaders::Projection;

#[derive(Clone, Debug, Default)]
pub struct Dimensions {
  pub window_width: f32,
  pub window_height: f32,
  pub hidpi_factor: f32,
}

impl Dimensions {
  pub fn new(window_width: f32, window_height: f32, hidpi_factor: f32) -> Dimensions {
    Dimensions {
      window_width,
      window_height,
      hidpi_factor,
    }
  }

  pub fn world_to_projection(&self, input: &CameraInputState) -> Projection {
    let view: Matrix4<f32> = get_view_matrix(input.distance);
    let aspect_ratio = self.window_width / self.window_height;
    get_projection(view, aspect_ratio)
  }
}

pub fn get_projection(view: Matrix4<f32>, aspect_ratio: f32) -> Projection {
  Projection {
    model: view.into(),
    view: view.into(),
    proj: cgmath::perspective(cgmath::Deg(75.0f32), aspect_ratio, 0.1, 4000.0).into(),
  }
}

pub fn get_view_matrix(view: f32) -> Matrix4<f32> {
  Matrix4::look_at(
    Point3::new(0.0, 0.0, view),
    Point3::new(0.0, 0.0, 0.0),
    Vector3::unit_y(),
  )
}
<commit_msg>Fix screen coordinate calculation in windowed mode<commit_after>use cgmath;
use cgmath::{Matrix4, Point3, Vector3};
use graphics::camera::CameraInputState;
use shaders::Projection;

#[derive(Clone, Debug, Default)]
pub struct Dimensions {
  pub window_width: f32,
  pub window_height: f32,
  pub hidpi_factor: f32,
}

impl Dimensions {
  pub fn new(window_width: f32, window_height: f32, hidpi_val: f32) -> Dimensions {
    let hidpi_factor = if cfg!(feature = "windowed") { 1.0 } else { hidpi_val };
    Dimensions {
      window_width,
      window_height,
      hidpi_factor,
    }
  }

  pub fn world_to_projection(&self, input: &CameraInputState) -> Projection {
    let view: Matrix4<f32> = get_view_matrix(input.distance);
    let aspect_ratio = self.window_width / self.window_height;
    get_projection(view, aspect_ratio)
  }
}

pub fn get_projection(view: Matrix4<f32>, aspect_ratio: f32) -> Projection {
  Projection {
    model: view.into(),
    view: view.into(),
    proj: cgmath::perspective(cgmath::Deg(75.0f32), aspect_ratio, 0.1, 4000.0).into(),
  }
}

pub fn get_view_matrix(view: f32) -> Matrix4<f32> {
  Matrix4::look_at(
    Point3::new(0.0, 0.0, view),
    Point3::new(0.0, 0.0, 0.0),
    Vector3::unit_y(),
  )
}
<|endoftext|>


<commit_before>use std::rc::Rc;

use gdk::ScreenExt;

use glib;

use gtk;
use gtk::prelude::*;
use gtk::{Menu, MenuBar, MenuItem, Notebook, ScrolledWindow, Window};

use app::{AppContext, AppContextPointer};
use app::config;
use gui::Gui;

mod menu_callbacks;

pub fn layout(gui: &Gui, ac: &AppContextPointer) {
    let window = gui.get_window();

    // Build the menu bar
    let menu_bar = build_menu_bar(ac, &window);

    // Layout main gui areas
    let frames = layout_frames(gui, ac);

    // Layout everything else
    let v_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
    v_box.pack_start(&menu_bar, false, false, 0);
    v_box.pack_start(&frames, true, true, 0);
    window.add(&v_box);

    configure_main_window(&window, ac);
}

fn build_menu_bar(ac: &AppContextPointer, win: &Window) -> MenuBar {
    //
    // The file menu.
    //

    // The open item
    let open_item = MenuItem::new_with_label("Open");
    let win1 = win.clone();
    let ac1 = Rc::clone(ac);
    open_item.connect_activate(move |mi| menu_callbacks::open_callback(mi, &ac1, &win1));

    // The quit item
    let quit_item = MenuItem::new_with_label("Quit");
    quit_item.connect_activate(|_| {
        gtk::main_quit();
    });

    // Build the file menu
    let file_menu = Menu::new();
    file_menu.append(&open_item);
    file_menu.append(&quit_item);

    // Build the file menu item
    let file = MenuItem::new_with_label("File");
    file.set_submenu(&file_menu);

    //
    // End the file menu
    //

    //
    // Build the menu bar
    //
    let menu_bar = MenuBar::new();
    menu_bar.append(&file);
    menu_bar
}

fn layout_frames(gui: &Gui, ac: &AppContextPointer) -> gtk::Paned {
    macro_rules! add_tab {
        ($notebook:ident, $widget:expr, $label:expr) => {
            $widget.set_property_margin(config::WIDGET_MARGIN);
            $notebook.add(&$widget);
            $notebook.set_tab_label_text(&$widget, $label);
        };
    }

    const BOX_SPACING: i32 = 3;

    let main_pane = gtk::Paned::new(gtk::Orientation::Horizontal);

    // Left pane
    let skew_t = gui.get_sounding_area();
    let h_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
    h_box.set_property_margin(config::WIDGET_MARGIN);
    h_box.pack_start(&skew_t, true, true, 0);

    // Set up scrolled window for text area.
    let text_win = ScrolledWindow::new(None, None);
    text_win.add(&gui.get_text_area());
    let v_text_box = gtk::Box::new(gtk::Orientation::Vertical, BOX_SPACING);
    v_text_box.pack_start(&::gui::text_area::make_header_text_area(), false, true, 0);
    v_text_box.pack_start(&text_win, true, true, 0);

    // Set up hbox for profiles
    let profile_box = gtk::Box::new(gtk::Orientation::Horizontal, BOX_SPACING);
    profile_box.pack_start(&gui.get_rh_omega_area(), true, true, 0);
    profile_box.pack_start(&gui.get_cloud_area(), true, true, 0);

    // Right pane
    let notebook = Notebook::new();
    add_tab!(notebook, gui.get_hodograph_area(), "Hodograph");
    add_tab!(notebook, profile_box, "Profiles");
    add_tab!(notebook, gui.get_index_area(), "Indexes");
    add_tab!(notebook, v_text_box, "Text");
    add_tab!(notebook, gui.get_control_area(), "Controls");
    notebook.set_tab_pos(gtk::PositionType::Right);

    main_pane.add1(&add_border_frame(&h_box));
    main_pane.add2(&notebook);

    let (width, height) = get_preferred_window_size(&skew_t, ac);
    let position = if width > height {
        height
    } else {
        width * 2 / 3
    };

    main_pane.set_position(position);

    main_pane
}

fn configure_main_window(window: &Window, ac: &AppContext) {
    let (width, height) = get_preferred_window_size(window, ac);

    // Set up window title, size, etc
    window.set_title("Sonde");
    window.set_default_size(width, height);
    window.set_decorated(true);
    window.show_all();
    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });
}

fn add_border_frame<P: glib::IsA<gtk::Widget>>(widget: &P) -> gtk::Frame {
    let f = gtk::Frame::new(None);
    f.add(widget);
    f.set_border_width(config::BORDER_WIDTH);
    f.set_hexpand(true);
    f.set_vexpand(true);
    f.set_shadow_type(gtk::ShadowType::In);

    f
}

fn get_preferred_window_size<T: WidgetExt>(widget: &T, ac: &AppContext) -> (i32, i32) {
    let config = ac.config.borrow();

    if let Some(screen) = widget.get_screen() {
        (screen.get_width() * 2 / 3, screen.get_height() * 2 / 3)
    } else {
        (config.window_width, config.window_height)
    }
}
<commit_msg>Fixed window size on smaller screens.<commit_after>use std::rc::Rc;

use gdk::ScreenExt;

use glib;

use gtk;
use gtk::prelude::*;
use gtk::{Menu, MenuBar, MenuItem, Notebook, ScrolledWindow, Window};

use app::{AppContext, AppContextPointer};
use app::config;
use gui::Gui;

mod menu_callbacks;

pub fn layout(gui: &Gui, ac: &AppContextPointer) {
    let window = gui.get_window();

    // Build the menu bar
    let menu_bar = build_menu_bar(ac, &window);

    // Layout main gui areas
    let frames = layout_frames(gui, ac);

    // Layout everything else
    let v_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
    v_box.pack_start(&menu_bar, false, false, 0);
    v_box.pack_start(&frames, true, true, 0);
    window.add(&v_box);

    configure_main_window(&window, ac);
}

fn build_menu_bar(ac: &AppContextPointer, win: &Window) -> MenuBar {
    //
    // The file menu.
    //

    // The open item
    let open_item = MenuItem::new_with_label("Open");
    let win1 = win.clone();
    let ac1 = Rc::clone(ac);
    open_item.connect_activate(move |mi| menu_callbacks::open_callback(mi, &ac1, &win1));

    // The quit item
    let quit_item = MenuItem::new_with_label("Quit");
    quit_item.connect_activate(|_| {
        gtk::main_quit();
    });

    // Build the file menu
    let file_menu = Menu::new();
    file_menu.append(&open_item);
    file_menu.append(&quit_item);

    // Build the file menu item
    let file = MenuItem::new_with_label("File");
    file.set_submenu(&file_menu);

    //
    // End the file menu
    //

    //
    // Build the menu bar
    //
    let menu_bar = MenuBar::new();
    menu_bar.append(&file);
    menu_bar
}

fn layout_frames(gui: &Gui, ac: &AppContextPointer) -> gtk::Paned {
    macro_rules! add_tab {
        ($notebook:ident, $widget:expr, $label:expr) => {
            $widget.set_property_margin(config::WIDGET_MARGIN);
            $notebook.add(&$widget);
            $notebook.set_tab_label_text(&$widget, $label);
        };
    }

    const BOX_SPACING: i32 = 2;

    let main_pane = gtk::Paned::new(gtk::Orientation::Horizontal);

    // Left pane
    let skew_t = gui.get_sounding_area();

    // Set up scrolled window for text area.
    let text_win = ScrolledWindow::new(None, None);
    text_win.add(&gui.get_text_area());
    let v_text_box = gtk::Box::new(gtk::Orientation::Vertical, BOX_SPACING);
    v_text_box.pack_start(&::gui::text_area::make_header_text_area(), false, true, 0);
    v_text_box.pack_start(&text_win, true, true, 0);

    // Set up hbox for profiles
    let profile_box = gtk::Box::new(gtk::Orientation::Horizontal, BOX_SPACING);
    profile_box.pack_start(&gui.get_rh_omega_area(), true, true, 0);
    profile_box.pack_start(&gui.get_cloud_area(), true, true, 0);

    // Right pane
    let notebook = Notebook::new();
    add_tab!(notebook, gui.get_hodograph_area(), "Hodograph");
    add_tab!(notebook, profile_box, "Profiles");
    add_tab!(notebook, gui.get_index_area(), "Indexes");
    add_tab!(notebook, v_text_box, "Text");
    add_tab!(notebook, gui.get_control_area(), "Controls");
    notebook.set_tab_pos(gtk::PositionType::Right);

    main_pane.add1(&add_border_frame(&skew_t));
    main_pane.add2(&notebook);

    let (width, height) = get_preferred_window_size(&skew_t, ac);
    let position = if width > height {
        height
    } else {
        width * 2 / 3
    };

    main_pane.set_position(position);

    main_pane
}

fn configure_main_window(window: &Window, ac: &AppContext) {
    let (width, height) = get_preferred_window_size(window, ac);

    // Set up window title, size, etc
    window.set_title("Sonde");
    window.set_default_size(width, height);
    window.set_decorated(true);
    window.show_all();
    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });
}

fn add_border_frame<P: glib::IsA<gtk::Widget>>(widget: &P) -> gtk::Frame {
    let f = gtk::Frame::new(None);
    f.add(widget);
    f.set_border_width(config::BORDER_WIDTH);
    f.set_hexpand(true);
    f.set_vexpand(true);
    f.set_shadow_type(gtk::ShadowType::In);

    f
}

fn get_preferred_window_size<T: WidgetExt>(widget: &T, ac: &AppContext) -> (i32, i32) {
    let config = ac.config.borrow();

    if let Some(screen) = widget.get_screen() {
        let mut width = screen.get_width() * 2 / 3;
        if config.window_width > width { width = config.window_width; }

        let mut height = screen.get_height() * 2 / 3;
        if config.window_height > height { height = config.window_height;}
        (width, height)
    } else {
        (config.window_width, config.window_height)
    }
}
<|endoftext|>