<commit_before>//! YAML Serialization
//!
//! This module provides YAML serialization with the type `Serializer`.

use crate::{error, Error, Result};
use serde::{ser, serde_if_integer128};
use std::{fmt, io, num, str};
use yaml_rust::{yaml, Yaml, YamlEmitter};

pub struct Serializer;

impl ser::Serializer for Serializer {
    type Ok = Yaml;
    type Error = Error;

    type SerializeSeq = SerializeArray;
    type SerializeTuple = SerializeArray;
    type SerializeTupleStruct = SerializeArray;
    type SerializeTupleVariant = SerializeTupleVariant;
    type SerializeMap = SerializeMap;
    type SerializeStruct = SerializeStruct;
    type SerializeStructVariant = SerializeStructVariant;

    fn serialize_bool(self, v: bool) -> Result<Yaml> {
        Ok(Yaml::Boolean(v))
    }

    fn serialize_i8(self, v: i8) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i16(self, v: i16) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i32(self, v: i32) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i64(self, v: i64) -> Result<Yaml> {
        Ok(Yaml::Integer(v))
    }

    serde_if_integer128! {
        #[allow(clippy::cast_possible_truncation)]
        fn serialize_i128(self, v: i128) -> Result<Yaml> {
            if v <= i64::max_value() as i128 && v >= i64::min_value() as i128 {
                self.serialize_i64(v as i64)
            } else {
                Ok(Yaml::Real(v.to_string()))
            }
        }
    }

    fn serialize_u8(self, v: u8) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u16(self, v: u16) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u32(self, v: u32) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u64(self, v: u64) -> Result<Yaml> {
        if v <= i64::max_value() as u64 {
            self.serialize_i64(v as i64)
        } else {
            Ok(Yaml::Real(v.to_string()))
        }
    }

    serde_if_integer128! {
        #[allow(clippy::cast_possible_truncation)]
        fn serialize_u128(self, v: u128) -> Result<Yaml> {
            if v <= i64::max_value() as u128 {
                self.serialize_i64(v as i64)
            } else {
                Ok(Yaml::Real(v.to_string()))
            }
        }
    }

    fn serialize_f32(self, v: f32) -> Result<Yaml> {
        self.serialize_f64(v as f64)
    }

    fn serialize_f64(self, v: f64) -> Result<Yaml> {
        Ok(Yaml::Real(match v.classify() {
            num::FpCategory::Infinite if v.is_sign_positive() => ".inf".into(),
            num::FpCategory::Infinite => "-.inf".into(),
            num::FpCategory::Nan => ".nan".into(),
            _ => {
                let mut buf = vec![];
                ::dtoa::write(&mut buf, v).unwrap();
                ::std::str::from_utf8(&buf).unwrap().into()
            }
        }))
    }

    fn serialize_char(self, value: char) -> Result<Yaml> {
        Ok(Yaml::String(value.to_string()))
    }

    fn serialize_str(self, value: &str) -> Result<Yaml> {
        Ok(Yaml::String(value.to_owned()))
    }

    fn serialize_bytes(self, value: &[u8]) -> Result<Yaml> {
        let vec = value.iter().map(|&b| Yaml::Integer(b as i64)).collect();
        Ok(Yaml::Array(vec))
    }

    fn serialize_unit(self) -> Result<Yaml> {
        Ok(Yaml::Null)
    }

    fn serialize_unit_struct(self, _name: &'static str) -> Result<Yaml> {
        self.serialize_unit()
    }

    fn serialize_unit_variant(
        self,
        _name: &str,
        _variant_index: u32,
        variant: &str,
    ) -> Result<Yaml> {
        Ok(Yaml::String(variant.to_owned()))
    }

    fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Yaml>
    where
        T: ser::Serialize,
    {
        value.serialize(self)
    }

    fn serialize_newtype_variant<T: ?Sized>(
        self,
        _name: &str,
        _variant_index: u32,
        variant: &str,
        value: &T,
    ) -> Result<Yaml>
    where
        T: ser::Serialize,
    {
        Ok(singleton_hash(to_yaml(variant)?, to_yaml(value)?))
    }

    fn serialize_none(self) -> Result<Yaml> {
        self.serialize_unit()
    }

    fn serialize_some<V: ?Sized>(self, value: &V) -> Result<Yaml>
    where
        V: ser::Serialize,
    {
        value.serialize(self)
    }

    fn serialize_seq(self, len: Option<usize>) -> Result<SerializeArray> {
        let array = match len {
            None => yaml::Array::new(),
            Some(len) => yaml::Array::with_capacity(len),
        };
        Ok(SerializeArray { array })
    }

    fn serialize_tuple(self, len: usize) -> Result<SerializeArray> {
        self.serialize_seq(Some(len))
    }

    fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result<SerializeArray> {
        self.serialize_seq(Some(len))
    }

    fn serialize_tuple_variant(
        self,
        _enum: &'static str,
        _idx: u32,
        variant: &'static str,
        len: usize,
    ) -> Result<SerializeTupleVariant> {
        Ok(SerializeTupleVariant {
            name: variant,
            array: yaml::Array::with_capacity(len),
        })
    }

    fn serialize_map(self, _len: Option<usize>) -> Result<SerializeMap> {
        Ok(SerializeMap {
            hash: yaml::Hash::new(),
            next_key: None,
        })
    }

    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<SerializeStruct> {
        Ok(SerializeStruct {
            hash: yaml::Hash::new(),
        })
    }

    fn serialize_struct_variant(
        self,
        _enum: &'static str,
        _idx: u32,
        variant: &'static str,
        _len: usize,
    ) -> Result<SerializeStructVariant> {
        Ok(SerializeStructVariant {
            name: variant,
            hash: yaml::Hash::new(),
        })
    }
}

#[doc(hidden)]
pub struct SerializeArray {
    array: yaml::Array,
}

#[doc(hidden)]
pub struct SerializeTupleVariant {
    name: &'static str,
    array: yaml::Array,
}

#[doc(hidden)]
pub struct SerializeMap {
    hash: yaml::Hash,
    next_key: Option<yaml::Yaml>,
}

#[doc(hidden)]
pub struct SerializeStruct {
    hash: yaml::Hash,
}

#[doc(hidden)]
pub struct SerializeStructVariant {
    name: &'static str,
    hash: yaml::Hash,
}

impl ser::SerializeSeq for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_element<T: ?Sized>(&mut self, elem: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        self.array.push(to_yaml(elem)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Array(self.array))
    }
}

impl ser::SerializeTuple for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_element<T: ?Sized>(&mut self, elem: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        ser::SerializeSeq::serialize_element(self, elem)
    }

    fn end(self) -> Result<Yaml> {
        ser::SerializeSeq::end(self)
    }
}

impl ser::SerializeTupleStruct for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, value: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        ser::SerializeSeq::serialize_element(self, value)
    }

    fn end(self) -> Result<Yaml> {
        ser::SerializeSeq::end(self)
    }
}

impl ser::SerializeTupleVariant for SerializeTupleVariant {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, v: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.array.push(to_yaml(v)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(singleton_hash(to_yaml(self.name)?, Yaml::Array(self.array)))
    }
}

impl ser::SerializeMap for SerializeMap {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        self.next_key = Some(to_yaml(key)?);
        Ok(())
    }

    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        match self.next_key.take() {
            Some(key) => self.hash.insert(key, to_yaml(value)?),
            None => panic!("serialize_value called before serialize_key"),
        };
        Ok(())
    }

    fn serialize_entry<K: ?Sized, V: ?Sized>(&mut self, key: &K, value: &V) -> Result<()>
    where
        K: ser::Serialize,
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(key)?, to_yaml(value)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Hash(self.hash))
    }
}

impl ser::SerializeStruct for SerializeStruct {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, key: &'static str, value: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(key)?, to_yaml(value)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Hash(self.hash))
    }
}

impl ser::SerializeStructVariant for SerializeStructVariant {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, field: &'static str, v: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(field)?, to_yaml(v)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(singleton_hash(to_yaml(self.name)?, Yaml::Hash(self.hash)))
    }
}

/// Serialize the given data structure as YAML into the IO stream.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_writer<W, T: ?Sized>(writer: W, value: &T) -> Result<()>
where
    W: io::Write,
    T: ser::Serialize,
{
    let doc = to_yaml(value)?;
    let mut writer_adapter = FmtToIoWriter { writer };
    YamlEmitter::new(&mut writer_adapter)
        .dump(&doc)
        .map_err(error::emitter)?;
    Ok(())
}

/// Serialize the given data structure as a YAML byte vector.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_vec<T: ?Sized>(value: &T) -> Result<Vec<u8>>
where
    T: ser::Serialize,
{
    let mut vec = Vec::with_capacity(128);
    to_writer(&mut vec, value)?;
    Ok(vec)
}

/// Serialize the given data structure as a String of YAML.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_string<T: ?Sized>(value: &T) -> Result<String>
where
    T: ser::Serialize,
{
    Ok(String::from_utf8(to_vec(value)?).map_err(error::string_utf8)?)
}

/// The yaml-rust library uses `fmt::Write` intead of `io::Write` so this is a
/// simple adapter.
struct FmtToIoWriter<W> {
    writer: W,
}

impl<W> fmt::Write for FmtToIoWriter<W>
where
    W: io::Write,
{
    fn write_str(&mut self, s: &str) -> fmt::Result {
        if self.writer.write(s.as_bytes()).is_err() {
            return Err(fmt::Error);
        }
        Ok(())
    }
}

fn to_yaml<T>(elem: T) -> Result<Yaml>
where
    T: ser::Serialize,
{
    elem.serialize(Serializer)
}

fn singleton_hash(k: Yaml, v: Yaml) -> Yaml {
    let mut hash = yaml::Hash::new();
    hash.insert(k, v);
    Yaml::Hash(hash)
}
<commit_msg>Resolve needless_question_mark clippy lint<commit_after>//! YAML Serialization
//!
//! This module provides YAML serialization with the type `Serializer`.

use crate::{error, Error, Result};
use serde::{ser, serde_if_integer128};
use std::{fmt, io, num, str};
use yaml_rust::{yaml, Yaml, YamlEmitter};

pub struct Serializer;

impl ser::Serializer for Serializer {
    type Ok = Yaml;
    type Error = Error;

    type SerializeSeq = SerializeArray;
    type SerializeTuple = SerializeArray;
    type SerializeTupleStruct = SerializeArray;
    type SerializeTupleVariant = SerializeTupleVariant;
    type SerializeMap = SerializeMap;
    type SerializeStruct = SerializeStruct;
    type SerializeStructVariant = SerializeStructVariant;

    fn serialize_bool(self, v: bool) -> Result<Yaml> {
        Ok(Yaml::Boolean(v))
    }

    fn serialize_i8(self, v: i8) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i16(self, v: i16) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i32(self, v: i32) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_i64(self, v: i64) -> Result<Yaml> {
        Ok(Yaml::Integer(v))
    }

    serde_if_integer128! {
        #[allow(clippy::cast_possible_truncation)]
        fn serialize_i128(self, v: i128) -> Result<Yaml> {
            if v <= i64::max_value() as i128 && v >= i64::min_value() as i128 {
                self.serialize_i64(v as i64)
            } else {
                Ok(Yaml::Real(v.to_string()))
            }
        }
    }

    fn serialize_u8(self, v: u8) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u16(self, v: u16) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u32(self, v: u32) -> Result<Yaml> {
        self.serialize_i64(v as i64)
    }

    fn serialize_u64(self, v: u64) -> Result<Yaml> {
        if v <= i64::max_value() as u64 {
            self.serialize_i64(v as i64)
        } else {
            Ok(Yaml::Real(v.to_string()))
        }
    }

    serde_if_integer128! {
        #[allow(clippy::cast_possible_truncation)]
        fn serialize_u128(self, v: u128) -> Result<Yaml> {
            if v <= i64::max_value() as u128 {
                self.serialize_i64(v as i64)
            } else {
                Ok(Yaml::Real(v.to_string()))
            }
        }
    }

    fn serialize_f32(self, v: f32) -> Result<Yaml> {
        self.serialize_f64(v as f64)
    }

    fn serialize_f64(self, v: f64) -> Result<Yaml> {
        Ok(Yaml::Real(match v.classify() {
            num::FpCategory::Infinite if v.is_sign_positive() => ".inf".into(),
            num::FpCategory::Infinite => "-.inf".into(),
            num::FpCategory::Nan => ".nan".into(),
            _ => {
                let mut buf = vec![];
                ::dtoa::write(&mut buf, v).unwrap();
                ::std::str::from_utf8(&buf).unwrap().into()
            }
        }))
    }

    fn serialize_char(self, value: char) -> Result<Yaml> {
        Ok(Yaml::String(value.to_string()))
    }

    fn serialize_str(self, value: &str) -> Result<Yaml> {
        Ok(Yaml::String(value.to_owned()))
    }

    fn serialize_bytes(self, value: &[u8]) -> Result<Yaml> {
        let vec = value.iter().map(|&b| Yaml::Integer(b as i64)).collect();
        Ok(Yaml::Array(vec))
    }

    fn serialize_unit(self) -> Result<Yaml> {
        Ok(Yaml::Null)
    }

    fn serialize_unit_struct(self, _name: &'static str) -> Result<Yaml> {
        self.serialize_unit()
    }

    fn serialize_unit_variant(
        self,
        _name: &str,
        _variant_index: u32,
        variant: &str,
    ) -> Result<Yaml> {
        Ok(Yaml::String(variant.to_owned()))
    }

    fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Yaml>
    where
        T: ser::Serialize,
    {
        value.serialize(self)
    }

    fn serialize_newtype_variant<T: ?Sized>(
        self,
        _name: &str,
        _variant_index: u32,
        variant: &str,
        value: &T,
    ) -> Result<Yaml>
    where
        T: ser::Serialize,
    {
        Ok(singleton_hash(to_yaml(variant)?, to_yaml(value)?))
    }

    fn serialize_none(self) -> Result<Yaml> {
        self.serialize_unit()
    }

    fn serialize_some<V: ?Sized>(self, value: &V) -> Result<Yaml>
    where
        V: ser::Serialize,
    {
        value.serialize(self)
    }

    fn serialize_seq(self, len: Option<usize>) -> Result<SerializeArray> {
        let array = match len {
            None => yaml::Array::new(),
            Some(len) => yaml::Array::with_capacity(len),
        };
        Ok(SerializeArray { array })
    }

    fn serialize_tuple(self, len: usize) -> Result<SerializeArray> {
        self.serialize_seq(Some(len))
    }

    fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result<SerializeArray> {
        self.serialize_seq(Some(len))
    }

    fn serialize_tuple_variant(
        self,
        _enum: &'static str,
        _idx: u32,
        variant: &'static str,
        len: usize,
    ) -> Result<SerializeTupleVariant> {
        Ok(SerializeTupleVariant {
            name: variant,
            array: yaml::Array::with_capacity(len),
        })
    }

    fn serialize_map(self, _len: Option<usize>) -> Result<SerializeMap> {
        Ok(SerializeMap {
            hash: yaml::Hash::new(),
            next_key: None,
        })
    }

    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<SerializeStruct> {
        Ok(SerializeStruct {
            hash: yaml::Hash::new(),
        })
    }

    fn serialize_struct_variant(
        self,
        _enum: &'static str,
        _idx: u32,
        variant: &'static str,
        _len: usize,
    ) -> Result<SerializeStructVariant> {
        Ok(SerializeStructVariant {
            name: variant,
            hash: yaml::Hash::new(),
        })
    }
}

#[doc(hidden)]
pub struct SerializeArray {
    array: yaml::Array,
}

#[doc(hidden)]
pub struct SerializeTupleVariant {
    name: &'static str,
    array: yaml::Array,
}

#[doc(hidden)]
pub struct SerializeMap {
    hash: yaml::Hash,
    next_key: Option<yaml::Yaml>,
}

#[doc(hidden)]
pub struct SerializeStruct {
    hash: yaml::Hash,
}

#[doc(hidden)]
pub struct SerializeStructVariant {
    name: &'static str,
    hash: yaml::Hash,
}

impl ser::SerializeSeq for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_element<T: ?Sized>(&mut self, elem: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        self.array.push(to_yaml(elem)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Array(self.array))
    }
}

impl ser::SerializeTuple for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_element<T: ?Sized>(&mut self, elem: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        ser::SerializeSeq::serialize_element(self, elem)
    }

    fn end(self) -> Result<Yaml> {
        ser::SerializeSeq::end(self)
    }
}

impl ser::SerializeTupleStruct for SerializeArray {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, value: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        ser::SerializeSeq::serialize_element(self, value)
    }

    fn end(self) -> Result<Yaml> {
        ser::SerializeSeq::end(self)
    }
}

impl ser::SerializeTupleVariant for SerializeTupleVariant {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, v: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.array.push(to_yaml(v)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(singleton_hash(to_yaml(self.name)?, Yaml::Array(self.array)))
    }
}

impl ser::SerializeMap for SerializeMap {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        self.next_key = Some(to_yaml(key)?);
        Ok(())
    }

    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<()>
    where
        T: ser::Serialize,
    {
        match self.next_key.take() {
            Some(key) => self.hash.insert(key, to_yaml(value)?),
            None => panic!("serialize_value called before serialize_key"),
        };
        Ok(())
    }

    fn serialize_entry<K: ?Sized, V: ?Sized>(&mut self, key: &K, value: &V) -> Result<()>
    where
        K: ser::Serialize,
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(key)?, to_yaml(value)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Hash(self.hash))
    }
}

impl ser::SerializeStruct for SerializeStruct {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, key: &'static str, value: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(key)?, to_yaml(value)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(Yaml::Hash(self.hash))
    }
}

impl ser::SerializeStructVariant for SerializeStructVariant {
    type Ok = yaml::Yaml;
    type Error = Error;

    fn serialize_field<V: ?Sized>(&mut self, field: &'static str, v: &V) -> Result<()>
    where
        V: ser::Serialize,
    {
        self.hash.insert(to_yaml(field)?, to_yaml(v)?);
        Ok(())
    }

    fn end(self) -> Result<Yaml> {
        Ok(singleton_hash(to_yaml(self.name)?, Yaml::Hash(self.hash)))
    }
}

/// Serialize the given data structure as YAML into the IO stream.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_writer<W, T: ?Sized>(writer: W, value: &T) -> Result<()>
where
    W: io::Write,
    T: ser::Serialize,
{
    let doc = to_yaml(value)?;
    let mut writer_adapter = FmtToIoWriter { writer };
    YamlEmitter::new(&mut writer_adapter)
        .dump(&doc)
        .map_err(error::emitter)?;
    Ok(())
}

/// Serialize the given data structure as a YAML byte vector.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_vec<T: ?Sized>(value: &T) -> Result<Vec<u8>>
where
    T: ser::Serialize,
{
    let mut vec = Vec::with_capacity(128);
    to_writer(&mut vec, value)?;
    Ok(vec)
}

/// Serialize the given data structure as a String of YAML.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// return an error.
pub fn to_string<T: ?Sized>(value: &T) -> Result<String>
where
    T: ser::Serialize,
{
    String::from_utf8(to_vec(value)?).map_err(error::string_utf8)
}

/// The yaml-rust library uses `fmt::Write` intead of `io::Write` so this is a
/// simple adapter.
struct FmtToIoWriter<W> {
    writer: W,
}

impl<W> fmt::Write for FmtToIoWriter<W>
where
    W: io::Write,
{
    fn write_str(&mut self, s: &str) -> fmt::Result {
        if self.writer.write(s.as_bytes()).is_err() {
            return Err(fmt::Error);
        }
        Ok(())
    }
}

fn to_yaml<T>(elem: T) -> Result<Yaml>
where
    T: ser::Serialize,
{
    elem.serialize(Serializer)
}

fn singleton_hash(k: Yaml, v: Yaml) -> Yaml {
    let mut hash = yaml::Hash::new();
    hash.insert(k, v);
    Yaml::Hash(hash)
}
<|endoftext|>


<commit_before>use super::board::{Field, HEIGHT, WIDTH};
use super::tetromino::{
    Point,
    Rotation,
    Tetromino,
    TetrominoType,
};

#[derive(Copy, Clone, Debug)]
pub enum Direction {
    Clockwise,
    CounterClockwise,
}

/// Rotates a Tetromino located inside a field in a specified direction
pub fn rotate(field: &Field, tetromino: &Tetromino, dir: Direction) -> Option<(Field, Tetromino)> {
    let mut field = field.clone();

    // Remove the original piece from the field. Since we cloned the field,
    // we are able to freely manipulate it without worry. Assuming the
    // rotation is possible, this will make performing it easier later on
    erase(&mut field, &tetromino);

    // Rotate the piece if possible
    match is_rotatable(&field, &tetromino, dir) {
        Some(rotated) => {
            do_rotation(&mut field, &rotated);
            Some((field, rotated))
        },

        None => None,
    }
}

/// Erases a Tetromino from a field
fn erase(field: &mut Field, tetromino: &Tetromino) {
    for &mino in tetromino.minos().iter() {
        let pos = tetromino.origin() + mino;
        field[pos.y as usize][pos.x as usize] = None;
    }
}

/// Determines if a Tetromino is rotatable within a specified field. Returns the rotated
/// Tetromino if the rotation is possible
fn is_rotatable(field: &Field, tetromino: &Tetromino, dir: Direction) -> Option<Tetromino> {
    let rotated = next_rotated_tetromino(tetromino, dir);

    for &mino in rotated.minos().iter() {
        let pos = tetromino.origin() + mino;

        // Determine if it is possible for the rotated mino to move to the new position.
        // If the new position is outside the bounds or is already occupied, then it is
        // not possible and the entire rotation fails
        if pos.x < 0 || pos.y < 0 || pos.x as usize >= WIDTH  || pos.y as usize >= HEIGHT
                || field[pos.y as usize][pos.x as usize].is_some() {
            return None;
        }
    }

    Some(rotated)
}

/// Performs the actual rotation
fn do_rotation(field: &mut Field, rotated: &Tetromino) {
    for &mino in rotated.minos().iter() {
        let pos = rotated.origin() + mino;
        field[pos.y as usize][pos.x as usize] = Some(rotated.tetromino_type());
    }
}

/// Gets the next rotation of a Tetromino in a specified direction
fn next_rotated_tetromino(tetromino: &Tetromino, dir: Direction) -> Tetromino {
    let rot = match dir {
        Direction::Clockwise => {
            match tetromino.rot() {
                Rotation::Spawn => Rotation::Right,
                Rotation::Right => Rotation::Rot2,
                Rotation::Rot2  => Rotation::Left,
                Rotation::Left  => Rotation::Spawn,
            }
        },

        Direction::CounterClockwise => {
            match tetromino.rot() {
                Rotation::Spawn => Rotation::Left,
                Rotation::Right => Rotation::Spawn,
                Rotation::Rot2  => Rotation::Right,
                Rotation::Left  => Rotation::Rot2,
            }
        }
    };

    Tetromino::new(tetromino.origin(), tetromino.tetromino_type(), rot)
}

const TESTS: usize = 5;
const DIRECTIONS: usize = 2;
const ROTS: usize = 4;

// Wall kick data as specified by the Super Rotation System
const WALL_KICKS: [[[Point; TESTS]; DIRECTIONS]; ROTS] = [SPAWN_WK, RIGHT_WK, ROT2_WK, LEFT_WK];
const WALL_KICKS_I: [[[Point; TESTS]; DIRECTIONS]; ROTS] = [SPAWN_WK_I, RIGHT_WK_I, ROT2_WK_I, LEFT_WK_I];

// The wall kick data for J, L, O, S, T, and Z
const SPAWN_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x:  1, y:  2 },
    ]
];

const RIGHT_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x:  1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x:  1, y: -2 },
    ]
];

const ROT2_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x:  1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y:  2 },
    ]
];

const LEFT_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x: -1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y:  1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y: -2 },
    ]
];

// The wall kick data for I
const SPAWN_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  1 },
        Point { x:  1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ]
];

const RIGHT_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  1 },
        Point { x: -1, y:  2 },
    ]
];

const ROT2_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y: -1 },
        Point { x: -1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  2 },
        Point { x: -2, y: -1 },
    ]
];

const LEFT_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  2 },
        Point { x: -2, y: -1 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ]
];<commit_msg>Add wall kicks that use the Super Rotation System guideline<commit_after>use super::board::{Field, HEIGHT, WIDTH};
use super::tetromino::{
    Point,
    Rotation,
    Tetromino,
    TetrominoType,
};

#[derive(Copy, Clone, Debug)]
pub enum Direction {
    Clockwise,
    CounterClockwise,
}

/// Rotates a Tetromino located inside a field in a specified direction
pub fn rotate(field: &Field, tetromino: &Tetromino, dir: Direction) -> Option<(Field, Tetromino)> {
    let mut field = field.clone();

    // Remove the original piece from the field. Since we cloned the field,
    // we are able to freely manipulate it without worry. Assuming the
    // rotation is possible, this will make performing it easier later on
    erase(&mut field, &tetromino);

    // Rotate the piece if possible
    match is_rotatable(&field, &tetromino, dir) {
        Some(rotated) => {
            do_rotation(&mut field, &rotated);
            Some((field, rotated))
        },

        None => None,
    }
}

/// Erases a Tetromino from a field
fn erase(field: &mut Field, tetromino: &Tetromino) {
    for &mino in tetromino.minos().iter() {
        let pos = tetromino.origin() + mino;
        field[pos.y as usize][pos.x as usize] = None;
    }
}

/// Determines if a Tetromino is rotatable within a specified field. Returns the rotated
/// Tetromino if the rotation is possible
fn is_rotatable(field: &Field, tetromino: &Tetromino, dir: Direction) -> Option<Tetromino> {
    let rotated = next_rotated_tetromino(tetromino, dir);

    for &mino in rotated.minos().iter() {
        let pos = tetromino.origin() + mino;

        // Determine if it is possible for the rotated mino to move to the new position.
        // If the new position is outside the bounds or is already occupied, then it is
        // not possible and we need to test for wall kicks
        if pos.x < 0 || pos.y < 0 || pos.x as usize >= WIDTH || pos.y as usize >= HEIGHT
                || field[pos.y as usize][pos.x as usize].is_some() {
            return wall_kick(field, &rotated, dir);
        }
    }

    Some(rotated)
}

/// Attempts to wall kick a Tetromino
fn wall_kick(field: &Field, tetromino: &Tetromino, dir: Direction) -> Option<Tetromino> {
    let tests = match tetromino.tetromino_type() {
        TetrominoType::I => WALL_KICKS_I[tetromino.rot() as usize][dir as usize].clone(),
        _ => WALL_KICKS[tetromino.rot() as usize][dir as usize].clone(),
    };

    let mut wall_kicked: Option<Tetromino> = None;

    for &test in tests.iter() {
        match test_wall_kick(field, tetromino, test) {
            Some(wall_kicked) => {
                return Some(wall_kicked)
            },

            None =>  { },
        }
    }

    None
}

/// Performs a wall kick test
fn test_wall_kick(field: &Field, tetromino: &Tetromino, test: Point) -> Option<Tetromino> {
     for &mino in tetromino.minos().iter() {
        let pos = tetromino.origin() + mino + test;

        if pos.x < 0 || pos.y < 0 || (pos.x as usize) >= WIDTH || (pos.y as usize) >= HEIGHT
                || field[pos.y as usize][pos.x  as usize].is_some() {
            return None;
        }
    }

    let mut tetromino = tetromino.clone();
    let new = tetromino.origin() + test;
    tetromino.set_origin(new);
    Some(tetromino)
}

/// Performs the actual rotation
fn do_rotation(field: &mut Field, rotated: &Tetromino) {
    for &mino in rotated.minos().iter() {
        let pos = rotated.origin() + mino;
        field[pos.y as usize][pos.x as usize] = Some(rotated.tetromino_type());
    }
}

/// Gets the next rotation of a Tetromino in a specified direction
fn next_rotated_tetromino(tetromino: &Tetromino, dir: Direction) -> Tetromino {
    let rot = match dir {
        Direction::Clockwise => {
            match tetromino.rot() {
                Rotation::Spawn => Rotation::Right,
                Rotation::Right => Rotation::Rot2,
                Rotation::Rot2  => Rotation::Left,
                Rotation::Left  => Rotation::Spawn,
            }
        },

        Direction::CounterClockwise => {
            match tetromino.rot() {
                Rotation::Spawn => Rotation::Left,
                Rotation::Right => Rotation::Spawn,
                Rotation::Rot2  => Rotation::Right,
                Rotation::Left  => Rotation::Rot2,
            }
        }
    };

    Tetromino::new(tetromino.origin(), tetromino.tetromino_type(), rot)
}

const TESTS: usize = 5;
const DIRECTIONS: usize = 2;
const ROTS: usize = 4;

// Wall kick data as specified by the Super Rotation System
const WALL_KICKS: [[[Point; TESTS]; DIRECTIONS]; ROTS] = [SPAWN_WK, RIGHT_WK, ROT2_WK, LEFT_WK];
const WALL_KICKS_I: [[[Point; TESTS]; DIRECTIONS]; ROTS] = [SPAWN_WK_I, RIGHT_WK_I, ROT2_WK_I, LEFT_WK_I];

// The wall kick data for J, L, O, S, T, and Z
const SPAWN_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x:  1, y:  2 },
    ]
];

const RIGHT_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x:  1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x:  1, y: -2 },
    ]
];

const ROT2_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x:  1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x:  1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y: -1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y:  2 },
    ]
];

const LEFT_WK: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y:  1 },
        Point { x:  0, y: -2 },
        Point { x: -1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x: -1, y:  1 },
        Point { x:  0, y:  2 },
        Point { x: -1, y: -2 },
    ]
];

// The wall kick data for I
const SPAWN_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  1 },
        Point { x:  1, y: -2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ]
];

const RIGHT_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  1 },
        Point { x: -1, y:  2 },
    ]
];

const ROT2_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y: -1 },
        Point { x: -1, y:  2 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  2 },
        Point { x: -2, y: -1 },
    ]
];

const LEFT_WK_I: [[Point; TESTS]; DIRECTIONS] = [
    [
        Point { x:  0, y:  0 },
        Point { x:  1, y:  0 },
        Point { x: -2, y:  0 },
        Point { x:  1, y:  2 },
        Point { x: -2, y: -1 },
    ],

    [
        Point { x:  0, y:  0 },
        Point { x: -1, y:  0 },
        Point { x:  2, y:  0 },
        Point { x: -1, y: -2 },
        Point { x:  2, y:  1 },
    ]
];<|endoftext|>


<commit_before>use std::{fs, io};
use std::os::unix::io::AsRawFd;

/// Is this stream an TTY?
#[cfg(not(target_os = "redox"))]
pub fn is_tty<T: AsRawFd>(stream: T) -> bool {
    use libc;

    unsafe { libc::isatty(stream.as_raw_fd()) == 1}
}

/// This will panic.
#[cfg(target_os = "redox")]
pub fn is_tty<T: AsRawFd>(_stream: T) -> bool {
    unimplemented!();
}

/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
#[cfg(target_os = "redox")]
pub fn get_tty() -> io::Result<fs::File> {
    fs::OpenOptions::new().read(true).write(true).open(try!(env::var("TTY")))
}

/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
#[cfg(not(target_os = "redox"))]
pub fn get_tty() -> io::Result<fs::File> {
    fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
}
<commit_msg>Fix Redox `get_tty` by adding `env` import (#40)<commit_after>use std::{fs, io};
use std::os::unix::io::AsRawFd;

/// Is this stream an TTY?
#[cfg(not(target_os = "redox"))]
pub fn is_tty<T: AsRawFd>(stream: T) -> bool {
    use libc;

    unsafe { libc::isatty(stream.as_raw_fd()) == 1}
}

/// This will panic.
#[cfg(target_os = "redox")]
pub fn is_tty<T: AsRawFd>(_stream: T) -> bool {
    unimplemented!();
}

/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
#[cfg(target_os = "redox")]
pub fn get_tty() -> io::Result<fs::File> {
    use std::env;
    fs::OpenOptions::new().read(true).write(true).open(try!(env::var("TTY")))
}

/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
#[cfg(not(target_os = "redox"))]
pub fn get_tty() -> io::Result<fs::File> {
    fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
}
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};

use lazy_static::lazy_static;
use log::warn;
use strum_macros::EnumString;
use walkdir;

use crate::config::{self, VutConfig};
use crate::template::{self, RenderTemplateError, TemplateInput};
use crate::util;
use crate::version::{self, Version};
use crate::version_source::{self, VersionSource};

const VUT_CONFIG_FILENAME: &str = ".vutconfig.toml";
const VUT_CONFIG_DEFAULT: &str = r###"
update_nested_sources = false
"###;

lazy_static! {
    static ref VUTEMPLATE_EXTENSION: &'static OsStr = OsStr::new("vutemplate");
}

#[derive(Debug, EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum BumpVersion {
    Major,
    Minor,
    Patch,
    Prerelease,
    Build,
}

pub enum VutError {
    AlreadyInit(PathBuf),
    OpenConfig(util::FileError),
    ReadConfig(io::Error),
    ParseConfig(Cow<'static, str>),
    WriteConfig(io::Error),
    NoVersionSource,
    VersionFileOpen(util::FileError),
    VersionFileRead(io::Error),
    VersionFileWrite(io::Error),
    TemplateGenerate(RenderTemplateError),
    Other(Cow<'static, str>),
}

pub struct Vut {
    root_path: PathBuf,
    config: VutConfig,
    authoritative_version_source: Box<dyn VersionSource>,
}

impl Vut {
    pub fn init(path: impl AsRef<Path>, version: Option<&Version>) -> Result<Self, VutError> {
        let path = path.as_ref();

        // Check if there is an existing Vut configuration for this path
        let vut: Option<Vut> = match Self::from_path(path) {
            Ok(vut) => {
                let existing_root_path = vut.get_root_path();

                if existing_root_path != path || existing_root_path.join(VUT_CONFIG_FILENAME).exists() {
                    Err(VutError::AlreadyInit(vut.get_root_path().to_path_buf()))
                } else {
                    Ok(Some(vut))
                }
            }
            Err(VutError::NoVersionSource) => Ok(None),
            Err(err) => Err(err),
        }?;

        // Construct config file path
        let config_file_path = path.join(VUT_CONFIG_FILENAME);

        // Create configuration file with default content
        util::create_file(&config_file_path)
            .map_err(|err| VutError::OpenConfig(err))?
            .write(VUT_CONFIG_DEFAULT.as_bytes())
            .map_err(|err| VutError::WriteConfig(err))?;

        let vut = if let Some(vut) = vut {
            // A version source was found, but no configuration file...
            // We need to support this in order to create a configuration file
            // for existing sources.

            Self {
                root_path: vut.root_path,
                config: VutConfig::from_str(VUT_CONFIG_DEFAULT)?,
                authoritative_version_source: vut.authoritative_version_source,
            }
        } else {
            // No version source was found...

            let version = version
                .map(|v| Cow::Borrowed(v))
                .unwrap_or_else(|| Cow::Owned(Version::new(0, 0, 0, None, None)));

            // Create a new version file source
            let mut source = version_source::VersionFileSource::new(path);

            // Set initial version
            source.set_version(&version)?;

            Self {
                root_path: path.to_path_buf(),
                config: VutConfig::default(),
                authoritative_version_source: Box::new(source),
            }
        };

        Ok(vut)
    }

    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, VutError> {
        let path = path.as_ref();

        let config_file_path = util::locate_config_file(path, VUT_CONFIG_FILENAME);

        let config = if let Some(path) = config_file_path.as_ref() {
            config::VutConfig::from_file(path)?
        } else {
            config::VutConfig::default()
        };

        let (root_path, authoritative_version_source) = if let Some(config_file_path) = config_file_path.as_ref() {
            let root_path = config_file_path.parent().unwrap().to_path_buf();

            // TODO: Implement the correct method to use here. This is wrong.
            let source = version_source::locate_version_source_from(path).ok_or_else(|| VutError::NoVersionSource)?;

            (root_path, source)
        } else {
            // No config file found.
            // Fall back to trying to locate a version source instead.

            let source = version_source::locate_version_source_from(path).ok_or_else(|| VutError::NoVersionSource)?;

            // TODO: Find a better way to display deprecation warning.
            warn!("DEPRECATED: Authoritative version source present with no config file. Create a .vutconfig in the root of the project.");

            let root_path = source.get_path().to_path_buf();

            (root_path, source)
        };

        Ok(Self {
            root_path: root_path.to_path_buf(),
            config,
            authoritative_version_source,
        })
    }

    pub fn from_current_dir() -> Result<Self, VutError> {
        let current_dir = env::current_dir().unwrap();

        Self::from_path(current_dir)
    }

    pub fn exists(&self) -> bool {
        self.authoritative_version_source.exists()
    }

    pub fn get_root_path(&self) -> &Path {
        &self.root_path
    }

    pub fn get_version(&self) -> Result<Version, VutError> {
        self.authoritative_version_source.get_version()
    }

    pub fn set_version(&mut self, version: &Version) -> Result<(), VutError> {
        self.authoritative_version_source.set_version(version)
    }

    pub fn bump_version(&mut self, bump_version: BumpVersion) -> Result<Version, VutError> {
        let version = self.get_version()?;

        let version = match bump_version {
            BumpVersion::Major => version.bump_major(),
            BumpVersion::Minor => version.bump_minor(),
            BumpVersion::Patch => version.bump_patch(),
            BumpVersion::Prerelease => version.bump_prerelease(),
            BumpVersion::Build => version.bump_build(),
        };

        self.authoritative_version_source.set_version(&version)?;

        Ok(version)
    }

    pub fn generate_template_input(&self) -> Result<TemplateInput, VutError> {
        let version = self.get_version()?;

        let mut template_input = TemplateInput::new();

        let split_prerelease = version
            .prerelease
            .as_ref()
            .map_or(None, |p| version::split_numbered_prerelease(p));
        let split_build = version
            .build
            .as_ref()
            .map_or(None, |b| version::split_numbered_prerelease(b));

        template_input
            .values
            .insert("FullVersion".to_owned(), version.to_string());
        template_input.values.insert(
            "Version".to_owned(),
            Version {
                build: None,
                ..version.clone()
            }
            .to_string(),
        );
        template_input.values.insert(
            "MajorMinorPatch".to_owned(),
            format!("{}.{}.{}", version.major, version.minor, version.patch),
        );
        template_input
            .values
            .insert("MajorMinor".to_owned(), format!("{}.{}", version.major, version.minor));
        template_input
            .values
            .insert("Major".to_owned(), format!("{}", version.major));
        template_input
            .values
            .insert("Minor".to_owned(), format!("{}", version.minor));
        template_input
            .values
            .insert("Patch".to_owned(), format!("{}", version.patch));
        template_input.values.insert(
            "Prerelease".to_owned(),
            version.prerelease.as_ref().map_or("", |p| p).to_owned(),
        );
        template_input.values.insert(
            "PrereleasePrefix".to_owned(),
            split_prerelease
                .and_then(|sp| Some(sp.0.to_owned()))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input.values.insert(
            "PrereleaseNumber".to_owned(),
            split_prerelease
                .and_then(|sp| Some(format!("{}", sp.1)))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input
            .values
            .insert("Build".to_owned(), version.build.as_ref().map_or("", |b| b).to_owned());
        template_input.values.insert(
            "BuildPrefix".to_owned(),
            split_build
                .and_then(|sp| Some(sp.0.to_owned()))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input.values.insert(
            "BuildNumber".to_owned(),
            split_build
                .and_then(|sp| Some(format!("{}", sp.1)))
                .unwrap_or_else(|| "".to_owned()),
        );

        Ok(template_input)
    }

    pub fn locate_templates_and_nested_sources(&self) -> (Vec<PathBuf>, Vec<Box<dyn VersionSource>>) {
        let root_path = &self.root_path;

        let dir_entries: Vec<walkdir::DirEntry> = walkdir::WalkDir::new(root_path)
            .into_iter()
            // Filter known VCS metadata directories
            .filter_entry(|entry| {
                !entry
                    .file_name()
                    .to_str()
                    .map(|s| s == ".git" || s == ".hg")
                    .unwrap_or(false)
            })
            .filter_map(|entry| entry.ok())
            .collect();

        let template_files: Vec<PathBuf> = dir_entries
            .iter()
            .map(|entry| entry.path())
            // Only include template files
            .filter(|path| match path.extension() {
                Some(ext) => ext == *VUTEMPLATE_EXTENSION,
                None => false,
            })
            // Transform Path references into owned PathBufs
            .map(|path| path.to_path_buf())
            .collect();

        let mut sources: Vec<Box<dyn VersionSource>> = Vec::new();

        if self.config.update_nested_sources {
            let dirs_iter = dir_entries
                .iter()
                .map(|entry| entry.path())
                // Only include directories
                .filter(|path| path.is_dir());

            for path in dirs_iter {
                if let Some(source) = version_source::version_source_from_path(&path) {
                    sources.push(source);
                }
            }
        }

        (template_files, sources)
    }

    pub fn generate_output(&self) -> Result<(), VutError> {
        let template_input = self.generate_template_input()?;
        let (template_files, nested_sources) = self.locate_templates_and_nested_sources();

        let mut processed_files: Vec<PathBuf> = Vec::new();
        let mut generated_files: Vec<PathBuf> = Vec::new();

        for file in template_files {
            let generated_file =
                template::generate_template::<template::processor::VutProcessor>(&file, &template_input, None)
                    .map_err(|err| VutError::TemplateGenerate(err))?;

            processed_files.push(file);
            generated_files.push(generated_file);
        }

        if self.config.update_nested_sources {
            let version = self.get_version()?;

            for mut source in nested_sources {
                source.set_version(&version)?;
            }
        }

        Ok(())
    }
}
<commit_msg>fix: Trim default configuration string before writing to file<commit_after>use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};

use lazy_static::lazy_static;
use log::warn;
use strum_macros::EnumString;
use walkdir;

use crate::config::{self, VutConfig};
use crate::template::{self, RenderTemplateError, TemplateInput};
use crate::util;
use crate::version::{self, Version};
use crate::version_source::{self, VersionSource};

const VUT_CONFIG_FILENAME: &str = ".vutconfig.toml";
const VUT_CONFIG_DEFAULT: &str = r###"
update_nested_sources = false
"###;

lazy_static! {
    static ref VUTEMPLATE_EXTENSION: &'static OsStr = OsStr::new("vutemplate");
}

#[derive(Debug, EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum BumpVersion {
    Major,
    Minor,
    Patch,
    Prerelease,
    Build,
}

pub enum VutError {
    AlreadyInit(PathBuf),
    OpenConfig(util::FileError),
    ReadConfig(io::Error),
    ParseConfig(Cow<'static, str>),
    WriteConfig(io::Error),
    NoVersionSource,
    VersionFileOpen(util::FileError),
    VersionFileRead(io::Error),
    VersionFileWrite(io::Error),
    TemplateGenerate(RenderTemplateError),
    Other(Cow<'static, str>),
}

pub struct Vut {
    root_path: PathBuf,
    config: VutConfig,
    authoritative_version_source: Box<dyn VersionSource>,
}

impl Vut {
    pub fn init(path: impl AsRef<Path>, version: Option<&Version>) -> Result<Self, VutError> {
        let path = path.as_ref();

        // Check if there is an existing Vut configuration for this path
        let vut: Option<Vut> = match Self::from_path(path) {
            Ok(vut) => {
                let existing_root_path = vut.get_root_path();

                if existing_root_path != path || existing_root_path.join(VUT_CONFIG_FILENAME).exists() {
                    Err(VutError::AlreadyInit(vut.get_root_path().to_path_buf()))
                } else {
                    Ok(Some(vut))
                }
            }
            Err(VutError::NoVersionSource) => Ok(None),
            Err(err) => Err(err),
        }?;

        // Construct config file path
        let config_file_path = path.join(VUT_CONFIG_FILENAME);

        // Create configuration file with default content
        util::create_file(&config_file_path)
            .map_err(|err| VutError::OpenConfig(err))?
            .write(VUT_CONFIG_DEFAULT.trim().as_bytes())
            .map_err(|err| VutError::WriteConfig(err))?;

        let vut = if let Some(vut) = vut {
            // A version source was found, but no configuration file...
            // We need to support this in order to create a configuration file
            // for existing sources.

            Self {
                root_path: vut.root_path,
                config: VutConfig::from_str(VUT_CONFIG_DEFAULT)?,
                authoritative_version_source: vut.authoritative_version_source,
            }
        } else {
            // No version source was found...

            let version = version
                .map(|v| Cow::Borrowed(v))
                .unwrap_or_else(|| Cow::Owned(Version::new(0, 0, 0, None, None)));

            // Create a new version file source
            let mut source = version_source::VersionFileSource::new(path);

            // Set initial version
            source.set_version(&version)?;

            Self {
                root_path: path.to_path_buf(),
                config: VutConfig::default(),
                authoritative_version_source: Box::new(source),
            }
        };

        Ok(vut)
    }

    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, VutError> {
        let path = path.as_ref();

        let config_file_path = util::locate_config_file(path, VUT_CONFIG_FILENAME);

        let config = if let Some(path) = config_file_path.as_ref() {
            config::VutConfig::from_file(path)?
        } else {
            config::VutConfig::default()
        };

        let (root_path, authoritative_version_source) = if let Some(config_file_path) = config_file_path.as_ref() {
            let root_path = config_file_path.parent().unwrap().to_path_buf();

            // TODO: Implement the correct method to use here. This is wrong.
            let source = version_source::locate_version_source_from(path).ok_or_else(|| VutError::NoVersionSource)?;

            (root_path, source)
        } else {
            // No config file found.
            // Fall back to trying to locate a version source instead.

            let source = version_source::locate_version_source_from(path).ok_or_else(|| VutError::NoVersionSource)?;

            // TODO: Find a better way to display deprecation warning.
            warn!("DEPRECATED: Authoritative version source present with no config file. Create a .vutconfig in the root of the project.");

            let root_path = source.get_path().to_path_buf();

            (root_path, source)
        };

        Ok(Self {
            root_path: root_path.to_path_buf(),
            config,
            authoritative_version_source,
        })
    }

    pub fn from_current_dir() -> Result<Self, VutError> {
        let current_dir = env::current_dir().unwrap();

        Self::from_path(current_dir)
    }

    pub fn exists(&self) -> bool {
        self.authoritative_version_source.exists()
    }

    pub fn get_root_path(&self) -> &Path {
        &self.root_path
    }

    pub fn get_version(&self) -> Result<Version, VutError> {
        self.authoritative_version_source.get_version()
    }

    pub fn set_version(&mut self, version: &Version) -> Result<(), VutError> {
        self.authoritative_version_source.set_version(version)
    }

    pub fn bump_version(&mut self, bump_version: BumpVersion) -> Result<Version, VutError> {
        let version = self.get_version()?;

        let version = match bump_version {
            BumpVersion::Major => version.bump_major(),
            BumpVersion::Minor => version.bump_minor(),
            BumpVersion::Patch => version.bump_patch(),
            BumpVersion::Prerelease => version.bump_prerelease(),
            BumpVersion::Build => version.bump_build(),
        };

        self.authoritative_version_source.set_version(&version)?;

        Ok(version)
    }

    pub fn generate_template_input(&self) -> Result<TemplateInput, VutError> {
        let version = self.get_version()?;

        let mut template_input = TemplateInput::new();

        let split_prerelease = version
            .prerelease
            .as_ref()
            .map_or(None, |p| version::split_numbered_prerelease(p));
        let split_build = version
            .build
            .as_ref()
            .map_or(None, |b| version::split_numbered_prerelease(b));

        template_input
            .values
            .insert("FullVersion".to_owned(), version.to_string());
        template_input.values.insert(
            "Version".to_owned(),
            Version {
                build: None,
                ..version.clone()
            }
            .to_string(),
        );
        template_input.values.insert(
            "MajorMinorPatch".to_owned(),
            format!("{}.{}.{}", version.major, version.minor, version.patch),
        );
        template_input
            .values
            .insert("MajorMinor".to_owned(), format!("{}.{}", version.major, version.minor));
        template_input
            .values
            .insert("Major".to_owned(), format!("{}", version.major));
        template_input
            .values
            .insert("Minor".to_owned(), format!("{}", version.minor));
        template_input
            .values
            .insert("Patch".to_owned(), format!("{}", version.patch));
        template_input.values.insert(
            "Prerelease".to_owned(),
            version.prerelease.as_ref().map_or("", |p| p).to_owned(),
        );
        template_input.values.insert(
            "PrereleasePrefix".to_owned(),
            split_prerelease
                .and_then(|sp| Some(sp.0.to_owned()))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input.values.insert(
            "PrereleaseNumber".to_owned(),
            split_prerelease
                .and_then(|sp| Some(format!("{}", sp.1)))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input
            .values
            .insert("Build".to_owned(), version.build.as_ref().map_or("", |b| b).to_owned());
        template_input.values.insert(
            "BuildPrefix".to_owned(),
            split_build
                .and_then(|sp| Some(sp.0.to_owned()))
                .unwrap_or_else(|| "".to_owned()),
        );
        template_input.values.insert(
            "BuildNumber".to_owned(),
            split_build
                .and_then(|sp| Some(format!("{}", sp.1)))
                .unwrap_or_else(|| "".to_owned()),
        );

        Ok(template_input)
    }

    pub fn locate_templates_and_nested_sources(&self) -> (Vec<PathBuf>, Vec<Box<dyn VersionSource>>) {
        let root_path = &self.root_path;

        let dir_entries: Vec<walkdir::DirEntry> = walkdir::WalkDir::new(root_path)
            .into_iter()
            // Filter known VCS metadata directories
            .filter_entry(|entry| {
                !entry
                    .file_name()
                    .to_str()
                    .map(|s| s == ".git" || s == ".hg")
                    .unwrap_or(false)
            })
            .filter_map(|entry| entry.ok())
            .collect();

        let template_files: Vec<PathBuf> = dir_entries
            .iter()
            .map(|entry| entry.path())
            // Only include template files
            .filter(|path| match path.extension() {
                Some(ext) => ext == *VUTEMPLATE_EXTENSION,
                None => false,
            })
            // Transform Path references into owned PathBufs
            .map(|path| path.to_path_buf())
            .collect();

        let mut sources: Vec<Box<dyn VersionSource>> = Vec::new();

        if self.config.update_nested_sources {
            let dirs_iter = dir_entries
                .iter()
                .map(|entry| entry.path())
                // Only include directories
                .filter(|path| path.is_dir());

            for path in dirs_iter {
                if let Some(source) = version_source::version_source_from_path(&path) {
                    sources.push(source);
                }
            }
        }

        (template_files, sources)
    }

    pub fn generate_output(&self) -> Result<(), VutError> {
        let template_input = self.generate_template_input()?;
        let (template_files, nested_sources) = self.locate_templates_and_nested_sources();

        let mut processed_files: Vec<PathBuf> = Vec::new();
        let mut generated_files: Vec<PathBuf> = Vec::new();

        for file in template_files {
            let generated_file =
                template::generate_template::<template::processor::VutProcessor>(&file, &template_input, None)
                    .map_err(|err| VutError::TemplateGenerate(err))?;

            processed_files.push(file);
            generated_files.push(generated_file);
        }

        if self.config.update_nested_sources {
            let version = self.get_version()?;

            for mut source in nested_sources {
                source.set_version(&version)?;
            }
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>use intern::Interner;
use json::{JsonValue, self};
use reqwest::{Client, self};
use reqwest::Url;
use std::io::{BufReader, Read};
use chrono::Duration;
use std::collections::HashMap;
use std::str;

use {Entry, Object, BaseInfo, AuraType, HealType};

#[derive(Debug)]
pub struct Iter<'a> {
    intern: &'a Interner,
    chunk: Result<JsonValue, u64>,
    next: usize,
    client: Client,
    base_url: String,
    names: HashMap<isize, &'a str>,
    print: bool,
}

fn get_json(client: &Client, url: Url) -> JsonValue {
    let resp = client.get(url).send().unwrap();
    assert_eq!(resp.status(), &reqwest::StatusCode::Ok);
    let mut buf = vec![];
    BufReader::new(resp).read_to_end(&mut buf).unwrap();
    json::parse(str::from_utf8(&buf).unwrap()).unwrap()
}

fn parse_fights<'a>(intern: &'a Interner, log: &str, api_key: &str) -> (Client, HashMap<isize, &'a str>, u64, u64) {
    let client = Client::new().unwrap();
    let mut base_url = "https://www.warcraftlogs.com:443/v1/report/fights/".to_owned();
    base_url.push_str(log);
    let url = Url::parse_with_params(&base_url, &[("api_key", api_key)]).unwrap();
    let json = get_json(&client, url);
    let end = json["end"].as_u64().unwrap() - json["start"].as_u64().unwrap();
    let mut map = HashMap::new();
    for f in json["friendlies"].members() {
        map.insert(f["id"].as_isize().unwrap(),
                   intern.intern(f["name"].as_str().unwrap()));
    }
    let start = json["fights"].members().filter(|x| x["boss"] != 0).map(|x| x["start_time"].as_u64()).nth(0);
    let start = start.unwrap().unwrap();
    (client, map, end, start)
}

pub fn iter<'a>(intern: &'a Interner, log: &str, api_key: &str,
                skip_to_first_boss: bool, actorname: Option<&str>) -> Iter<'a> {
    let (client, names, end, first_boss) = parse_fights(intern, log, api_key);
    let mut base_url = "https://www.warcraftlogs.com:443/v1/report/events/".to_owned();
    base_url.push_str(log);
    let start = if skip_to_first_boss { first_boss } else { 0 };
    let mut args = vec![("api_key", api_key.to_string()), ("end", end.to_string())];
    if let Some(name) = actorname {
        // No, despite the sourceID field in the output you can't
        // filter on it (I think source.id is probably guid), and
        // source.name won't match a combatantinfo.
        //
        // actorid does work, but then you can't get encounter info,
        // and I'd have to store that separately or something rather
        // than leaving them in the event stream
        args.push(("filter", format!(r#"type="encounterstart" or type="encounterend" or type="combatantinfo" or source.name = "{}""#, name)));
    }
    base_url = Url::parse_with_params(&base_url, &args)
        .unwrap().into_string();
    Iter {
        intern: intern,
        chunk: Err(start),
        next: 0,
        client: client,
        base_url: base_url,
        names: names,
        print: false,
    }
}

impl<'a> Iter<'a> {
    fn fixup_chunk(&self, json: &mut JsonValue) {
        if json["events"].len() == 0 {
            return;
        }

        // Work around WCL API bugs:
        // 1. If the start time is exactly the timestamp of an encounterend, the encounterend is skipped
        // 2. Sometimes nextPageTimestamp isn't sent
        //
        // So decrement the timestamp (or insert one at the last event) and remove any elements at that timestamp
        let ts = json["nextPageTimestamp"].as_i64().map(|t| t - 1).unwrap_or_else(
            || json["events"].members().rev().nth(0).unwrap()["timestamp"].as_i64().unwrap());

        // We're probably legitimately at the end
        if json["events"][0]["timestamp"] == ts {
            return;
        }

        if json["nextPageTimestamp"].as_i64().is_none() {
            println!("missing");
        }

        if let JsonValue::Array(ref mut array) = json["events"] {
            let mut i = array.len() - 1;

            while array[i]["timestamp"] == ts {
                if array[i]["type"] != "encounterend" {
                    array.remove(i);
                }
                i -= 1;
            }

        }
        json["nextPageTimestamp"] = JsonValue::from(ts);

    }
    fn load_chunk(&mut self, next_start: u64) {
        let mut json = get_json(&self.client, Url::parse_with_params(&self.base_url, &[("start", &next_start.to_string())]).unwrap());
        self.fixup_chunk(&mut json);

        self.chunk = Ok(json);
        self.next = 0;
    }
    fn parse_object(&self, id: isize) -> Object<'a> {
        let idstr = self.intern.intern(&id.to_string());
        Object {
            id: idstr,
            name: *self.names.get(&id).unwrap_or(&idstr),
        }
    }
    fn parse_base(&self, json: &JsonValue) -> BaseInfo<'a> {
        fn get_id(json: &JsonValue, base: &str, baseid: &str) -> isize {
            if let Some(x) = json[baseid].as_isize() {
                x
            } else {
                // normal ids are 1+. "Environment" will have guid 0
                -json[base]["guid"].as_isize().unwrap()
            }
        }
        BaseInfo {
            timestamp: Duration::milliseconds(json["timestamp"].as_i64().unwrap()),
            // wcl uses a different setup for pets, don't worry about it?
            src: self.parse_object(get_id(json, "source", "sourceID")),
            dst: self.parse_object(get_id(json, "target", "targetID")),
            src_flags1: 0, src_flags2: 0,
            dst_flags1: 0, dst_flags2: 0,
        }
    }
    fn parse_entry(&self, json: &JsonValue) -> Entry<'a> {
        //println!("{}", json);
        let ts = Duration::milliseconds(json["timestamp"].as_i64().unwrap());
        let intern = self.intern;
        let ty = json["type"].as_str().unwrap();
        match ty {
            "encounterstart" =>
                Entry::EncounterStart {
                    ts: ts,
                    name: intern.intern(json["name"].as_str().unwrap()),
                    id: json["encounterID"].as_u32().unwrap(),
                    difficulty: json["difficulty"].as_u16().unwrap(),
                },
            "encounterend" =>
                Entry::EncounterEnd {
                    ts: ts,
                    name: intern.intern(json["name"].as_str().unwrap()),
                    id: json["encounterID"].as_u32().unwrap(),
                    difficulty: json["difficulty"].as_u16().unwrap(),
                    kill: json["kill"] == true,
                },
            "combatantinfo" =>
                Entry::Info {
                    ts: ts,
                    id: intern.intern(&json["sourceID"].to_string()),
                    strength: json["strength"].as_u32().unwrap(),
                    agi: json["agility"].as_u32().unwrap(),
                    sta: json["stamina"].as_u32().unwrap(),
                    int: json["intellect"].as_u32().unwrap(),
                    dodge: json["dodge"].as_u32().unwrap(),
                    parry: json["parry"].as_u32().unwrap(),
                    block: json["block"].as_u32().unwrap(),
                    critm: json["critMelee"].as_u32().unwrap(),
                    critr: json["critRanged"].as_u32().unwrap(),
                    crits: json["critSpell"].as_u32().unwrap(),
                    spd: json["speed"].as_u32().unwrap(),
                    steal: json["leech"].as_u32().unwrap(),
                    hastem: json["hasteMelee"].as_u32().unwrap(),
                    hastr: json["hasteRanged"].as_u32().unwrap(),
                    hastes: json["hasteSpell"].as_u32().unwrap(),
                    avd: json["avoidance"].as_u32().unwrap(),
                    mastery: json["mastery"].as_u32().unwrap(),
                    versm: json["versatilityDamageDone"].as_u32().unwrap(),
                    versr: json["versatilityHealingDone"].as_u32().unwrap(),
                    verss: json["versatilityDamageReduction"].as_u32().unwrap(),
                    armor: json["armor"].as_u32().unwrap(),
                },
            "heal" => {
                let effective = json["amount"].as_u64().unwrap();
                let overheal = json["overheal"].as_u64().unwrap_or(0);
                Entry::Heal {
                    ty: if json["tick"] == true { HealType::Periodic } else { HealType::Heal },
                    base: self.parse_base(json),
                    id: json["ability"]["guid"].as_u32().unwrap(),
                    spell: intern.intern(json["ability"]["name"].as_str().unwrap()),
                    flags: 0,
                    hp: json["hitPoints"].as_u64().unwrap(),
                    maxhp: json["maxHitPoints"].as_u64().unwrap(),
                    heal: effective + overheal,
                    overheal: overheal,
                    crit: json["hitType"] == 2
                }
            },
            "applybuff" | "removebuff" | "refreshbuff" | "applybuffstack" | "removebuffstack" |
            "applydebuff" | "removedebuff" | "refreshdebuff" | "applydebuffstack" | "removedebuffstack" => {
                let buff = !ty.contains("debuff");
                let ty = if ty.ends_with("stack") {
                    AuraType::Stack
                } else if ty.starts_with("apply") {
                    AuraType::Apply
                } else if ty.starts_with("remove") {
                    AuraType::Remove
                } else {
                    AuraType::Refresh
                };
                Entry::Aura {
                    ty: ty,
                    base: self.parse_base(json),
                    id: json["ability"]["guid"].as_u32().unwrap(),
                    aura: intern.intern(json["ability"]["name"].as_str().unwrap()),
                    flags: 0,
                    buff: buff,
                }
            }
            _ => Entry::Unknown(ts, intern.intern(&json.to_string()))
        }
    }

}

impl<'a> Iterator for Iter<'a> {
    type Item = Entry<'a>;
    fn next(&mut self) -> Option<Entry<'a>> {
        if let Err(next) = self.chunk {
            self.load_chunk(next);
        }
        let chunk = ::std::mem::replace(&mut self.chunk, Err(0)).unwrap();
        let len = chunk["events"].len();
        if len == 0 {
            return None;
        }
        let ret = self.parse_entry(&chunk["events"][self.next]);
        self.next += 1;
        if self.next < len {
            self.chunk = Ok(chunk);
        } else {
            self.chunk = Err(chunk["nextPageTimestamp"].as_u64().unwrap());
        }
        Some(ret)
    }
}

<commit_msg>WCL fixed one bug, and the legitimately-at-the-end thing caused other issues<commit_after>use intern::Interner;
use json::{JsonValue, self};
use reqwest::{Client, self};
use reqwest::Url;
use std::io::{BufReader, Read};
use chrono::Duration;
use std::collections::HashMap;
use std::str;

use {Entry, Object, BaseInfo, AuraType, HealType};

#[derive(Debug)]
pub struct Iter<'a> {
    intern: &'a Interner,
    chunk: Result<JsonValue, u64>,
    next: usize,
    client: Client,
    base_url: String,
    names: HashMap<isize, &'a str>,
    print: bool,
}

fn get_json(client: &Client, url: Url) -> JsonValue {
    let resp = client.get(url).send().unwrap();
    assert_eq!(resp.status(), &reqwest::StatusCode::Ok);
    let mut buf = vec![];
    BufReader::new(resp).read_to_end(&mut buf).unwrap();
    json::parse(str::from_utf8(&buf).unwrap()).unwrap()
}

fn parse_fights<'a>(intern: &'a Interner, log: &str, api_key: &str) -> (Client, HashMap<isize, &'a str>, u64, u64) {
    let client = Client::new().unwrap();
    let mut base_url = "https://www.warcraftlogs.com:443/v1/report/fights/".to_owned();
    base_url.push_str(log);
    let url = Url::parse_with_params(&base_url, &[("api_key", api_key)]).unwrap();
    let json = get_json(&client, url);
    let end = json["end"].as_u64().unwrap() - json["start"].as_u64().unwrap();
    let mut map = HashMap::new();
    for f in json["friendlies"].members() {
        map.insert(f["id"].as_isize().unwrap(),
                   intern.intern(f["name"].as_str().unwrap()));
    }
    let start = json["fights"].members().filter(|x| x["boss"] != 0).map(|x| x["start_time"].as_u64()).nth(0);
    let start = start.unwrap().unwrap();
    (client, map, end, start)
}

pub fn iter<'a>(intern: &'a Interner, log: &str, api_key: &str,
                skip_to_first_boss: bool, actorname: Option<&str>) -> Iter<'a> {
    let (client, names, end, first_boss) = parse_fights(intern, log, api_key);
    let mut base_url = "https://www.warcraftlogs.com:443/v1/report/events/".to_owned();
    base_url.push_str(log);
    let start = if skip_to_first_boss { first_boss } else { 0 };
    let mut args = vec![("api_key", api_key.to_string()), ("end", end.to_string())];
    if let Some(name) = actorname {
        // No, despite the sourceID field in the output you can't
        // filter on it (I think source.id is probably guid), and
        // source.name won't match a combatantinfo.
        //
        // actorid does work, but then you can't get encounter info,
        // and I'd have to store that separately or something rather
        // than leaving them in the event stream
        args.push(("filter", format!(r#"type="encounterstart" or type="encounterend" or type="combatantinfo" or source.name = "{}""#, name)));
    }
    base_url = Url::parse_with_params(&base_url, &args)
        .unwrap().into_string();
    Iter {
        intern: intern,
        chunk: Err(start),
        next: 0,
        client: client,
        base_url: base_url,
        names: names,
        print: false,
    }
}

impl<'a> Iter<'a> {
    fn fixup_chunk(&self, json: &mut JsonValue) {
        if json["events"].len() == 0 || json["nextPageTimestamp"].as_i64().is_some() {
            return;
        }

        // Work around WCL API bug where sometimes nextPageTimestamp
        // isn't sent. Also we are set up to only expect an empty
        // events array as the end
        let last_ts = json["events"].members().rev().nth(0).unwrap()["timestamp"].as_i64().unwrap();

        json["nextPageTimestamp"] = JsonValue::from(last_ts + 1);

    }
    fn load_chunk(&mut self, next_start: u64) {
        let mut json = get_json(&self.client, Url::parse_with_params(&self.base_url, &[("start", &next_start.to_string())]).unwrap());
        self.fixup_chunk(&mut json);

        self.chunk = Ok(json);
        self.next = 0;
    }
    fn parse_object(&self, id: isize) -> Object<'a> {
        let idstr = self.intern.intern(&id.to_string());
        Object {
            id: idstr,
            name: *self.names.get(&id).unwrap_or(&idstr),
        }
    }
    fn parse_base(&self, json: &JsonValue) -> BaseInfo<'a> {
        fn get_id(json: &JsonValue, base: &str, baseid: &str) -> isize {
            if let Some(x) = json[baseid].as_isize() {
                x
            } else {
                // normal ids are 1+. "Environment" will have guid 0
                -json[base]["guid"].as_isize().unwrap()
            }
        }
        BaseInfo {
            timestamp: Duration::milliseconds(json["timestamp"].as_i64().unwrap()),
            // wcl uses a different setup for pets, don't worry about it?
            src: self.parse_object(get_id(json, "source", "sourceID")),
            dst: self.parse_object(get_id(json, "target", "targetID")),
            src_flags1: 0, src_flags2: 0,
            dst_flags1: 0, dst_flags2: 0,
        }
    }
    fn parse_entry(&self, json: &JsonValue) -> Entry<'a> {
        //println!("{}", json);
        let ts = Duration::milliseconds(json["timestamp"].as_i64().unwrap());
        let intern = self.intern;
        let ty = json["type"].as_str().unwrap();
        match ty {
            "encounterstart" =>
                Entry::EncounterStart {
                    ts: ts,
                    name: intern.intern(json["name"].as_str().unwrap()),
                    id: json["encounterID"].as_u32().unwrap(),
                    difficulty: json["difficulty"].as_u16().unwrap(),
                },
            "encounterend" =>
                Entry::EncounterEnd {
                    ts: ts,
                    name: intern.intern(json["name"].as_str().unwrap()),
                    id: json["encounterID"].as_u32().unwrap(),
                    difficulty: json["difficulty"].as_u16().unwrap(),
                    kill: json["kill"] == true,
                },
            "combatantinfo" =>
                Entry::Info {
                    ts: ts,
                    id: intern.intern(&json["sourceID"].to_string()),
                    strength: json["strength"].as_u32().unwrap(),
                    agi: json["agility"].as_u32().unwrap(),
                    sta: json["stamina"].as_u32().unwrap(),
                    int: json["intellect"].as_u32().unwrap(),
                    dodge: json["dodge"].as_u32().unwrap(),
                    parry: json["parry"].as_u32().unwrap(),
                    block: json["block"].as_u32().unwrap(),
                    critm: json["critMelee"].as_u32().unwrap(),
                    critr: json["critRanged"].as_u32().unwrap(),
                    crits: json["critSpell"].as_u32().unwrap(),
                    spd: json["speed"].as_u32().unwrap(),
                    steal: json["leech"].as_u32().unwrap(),
                    hastem: json["hasteMelee"].as_u32().unwrap(),
                    hastr: json["hasteRanged"].as_u32().unwrap(),
                    hastes: json["hasteSpell"].as_u32().unwrap(),
                    avd: json["avoidance"].as_u32().unwrap(),
                    mastery: json["mastery"].as_u32().unwrap(),
                    versm: json["versatilityDamageDone"].as_u32().unwrap(),
                    versr: json["versatilityHealingDone"].as_u32().unwrap(),
                    verss: json["versatilityDamageReduction"].as_u32().unwrap(),
                    armor: json["armor"].as_u32().unwrap(),
                },
            "heal" => {
                let effective = json["amount"].as_u64().unwrap();
                let overheal = json["overheal"].as_u64().unwrap_or(0);
                Entry::Heal {
                    ty: if json["tick"] == true { HealType::Periodic } else { HealType::Heal },
                    base: self.parse_base(json),
                    id: json["ability"]["guid"].as_u32().unwrap(),
                    spell: intern.intern(json["ability"]["name"].as_str().unwrap()),
                    flags: 0,
                    hp: json["hitPoints"].as_u64().unwrap(),
                    maxhp: json["maxHitPoints"].as_u64().unwrap(),
                    heal: effective + overheal,
                    overheal: overheal,
                    crit: json["hitType"] == 2
                }
            },
            "applybuff" | "removebuff" | "refreshbuff" | "applybuffstack" | "removebuffstack" |
            "applydebuff" | "removedebuff" | "refreshdebuff" | "applydebuffstack" | "removedebuffstack" => {
                let buff = !ty.contains("debuff");
                let ty = if ty.ends_with("stack") {
                    AuraType::Stack
                } else if ty.starts_with("apply") {
                    AuraType::Apply
                } else if ty.starts_with("remove") {
                    AuraType::Remove
                } else {
                    AuraType::Refresh
                };
                Entry::Aura {
                    ty: ty,
                    base: self.parse_base(json),
                    id: json["ability"]["guid"].as_u32().unwrap(),
                    aura: intern.intern(json["ability"]["name"].as_str().unwrap()),
                    flags: 0,
                    buff: buff,
                }
            }
            _ => Entry::Unknown(ts, intern.intern(&json.to_string()))
        }
    }

}

impl<'a> Iterator for Iter<'a> {
    type Item = Entry<'a>;
    fn next(&mut self) -> Option<Entry<'a>> {
        if let Err(next) = self.chunk {
            self.load_chunk(next);
        }
        let chunk = ::std::mem::replace(&mut self.chunk, Err(0)).unwrap();
        let len = chunk["events"].len();
        if len == 0 {
            return None;
        }
        let ret = self.parse_entry(&chunk["events"][self.next]);
        self.next += 1;
        if self.next < len {
            self.chunk = Ok(chunk);
        } else {
            self.chunk = Err(chunk["nextPageTimestamp"].as_u64().unwrap());
        }
        Some(ret)
    }
}

<|endoftext|>


<commit_before>//! A chat server that broadcasts a message to all connections.
//!
//! This is a simple line-based server which accepts WebSocket connections,
//! reads lines from those connections, and broadcasts the lines to all other
//! connected clients.
//!
//! You can test this out by running:
//!
//!     cargo run --example server 127.0.0.1:12345
//!
//! And then in another window run:
//!
//!     cargo run --example client ws://127.0.0.1:12345/
//!
//! You can run the second command in multiple windows and then chat between the
//! two, seeing the messages from the other client as they're received. For all
//! connected clients they'll all join the same room and see everyone else's
//! messages.

use std::env;
use std::io::Error;

use futures::channel::mpsc::UnboundedSender;
use futures::stream::SplitStream;
use futures::{SinkExt, StreamExt};
use log::*;
use std::collections::HashMap;
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::{Arc, Mutex};
use tokio::net::{TcpListener, TcpStream};
use tungstenite::protocol::Message;

type Tx = UnboundedSender<Message>;

type WsRx = SplitStream<tokio_tungstenite::WebSocketStream<TcpStream>>;

type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;

async fn handle_message(addr: SocketAddr, peer_map: PeerMap, ws_rx: WsRx) {
    let mut ws_rx = ws_rx;
    while let Some(msg) = ws_rx.next().await {
        let msg = msg.expect("Failed to get response");
        info!(
            "Received a message from {}: {}",
            addr,
            msg.to_text().unwrap()
        );

        for peer in peer_map.lock().unwrap().iter_mut() {
            let _ = peer.1.unbounded_send(msg.clone().into());
        }
    }
}

async fn accept_connection(peer_map: PeerMap, raw_stream: TcpStream) {
    let addr = raw_stream
        .peer_addr()
        .expect("connected streams should have a peer address");
    info!("Peer address: {}", addr);

    let ws_stream = tokio_tungstenite::accept_async(raw_stream)
        .await
        .expect("Error during the websocket handshake occurred");

    info!("New WebSocket connection: {}", addr);

    let (mut ws_tx, ws_rx) = ws_stream.split();
    let (msg_tx, mut msg_rx) = futures::channel::mpsc::unbounded();

    //We want to be able to send data to a specific peer, use this tx.
    peer_map.lock().unwrap().insert(addr, msg_tx);

    //Handle incoming messages
    tokio::spawn(handle_message(addr, peer_map.clone(), ws_rx));

    //If msg_rx get's some data, send it on the websocket.
    while let Some(msg) = msg_rx.next().await {
        info!("Sending message to {}", addr);
        ws_tx.send(msg).await.expect("Failed to send request");
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let _ = env_logger::try_init();
    let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
    let addr = addr
        .to_socket_addrs()
        .expect("Not a valid address")
        .next()
        .expect("Not a socket address");

    let hm: HashMap<SocketAddr, Tx> = HashMap::new();
    let state = PeerMap::new(Mutex::new(hm));

    // Create the event loop and TCP listener we'll accept connections on.
    let try_socket = TcpListener::bind(&addr).await;
    let mut listener = try_socket.expect("Failed to bind");
    info!("Listening on: {}", addr);

    while let Ok((stream, _)) = listener.accept().await {
        tokio::spawn(accept_connection(state.clone(), stream));
    }

    Ok(())
}
<commit_msg>Fix a couple of clippy warnings in the new server example<commit_after>//! A chat server that broadcasts a message to all connections.
//!
//! This is a simple line-based server which accepts WebSocket connections,
//! reads lines from those connections, and broadcasts the lines to all other
//! connected clients.
//!
//! You can test this out by running:
//!
//!     cargo run --example server 127.0.0.1:12345
//!
//! And then in another window run:
//!
//!     cargo run --example client ws://127.0.0.1:12345/
//!
//! You can run the second command in multiple windows and then chat between the
//! two, seeing the messages from the other client as they're received. For all
//! connected clients they'll all join the same room and see everyone else's
//! messages.

use std::env;
use std::io::Error;

use futures::channel::mpsc::UnboundedSender;
use futures::stream::SplitStream;
use futures::{SinkExt, StreamExt};
use log::*;
use std::collections::HashMap;
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::{Arc, Mutex};
use tokio::net::{TcpListener, TcpStream};
use tungstenite::protocol::Message;

type Tx = UnboundedSender<Message>;

type WsRx = SplitStream<tokio_tungstenite::WebSocketStream<TcpStream>>;

type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;

async fn handle_message(addr: SocketAddr, peer_map: PeerMap, ws_rx: WsRx) {
    let mut ws_rx = ws_rx;
    while let Some(msg) = ws_rx.next().await {
        let msg = msg.expect("Failed to get response");
        info!(
            "Received a message from {}: {}",
            addr,
            msg.to_text().unwrap()
        );

        for peer in peer_map.lock().unwrap().iter_mut() {
            let _ = peer.1.unbounded_send(msg.clone());
        }
    }
}

async fn accept_connection(peer_map: PeerMap, raw_stream: TcpStream) {
    let addr = raw_stream
        .peer_addr()
        .expect("connected streams should have a peer address");
    info!("Peer address: {}", addr);

    let ws_stream = tokio_tungstenite::accept_async(raw_stream)
        .await
        .expect("Error during the websocket handshake occurred");

    info!("New WebSocket connection: {}", addr);

    let (mut ws_tx, ws_rx) = ws_stream.split();
    let (msg_tx, mut msg_rx) = futures::channel::mpsc::unbounded();

    //We want to be able to send data to a specific peer, use this tx.
    peer_map.lock().unwrap().insert(addr, msg_tx);

    //Handle incoming messages
    tokio::spawn(handle_message(addr, peer_map.clone(), ws_rx));

    //If msg_rx get's some data, send it on the websocket.
    while let Some(msg) = msg_rx.next().await {
        info!("Sending message to {}", addr);
        ws_tx.send(msg).await.expect("Failed to send request");
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let _ = env_logger::try_init();
    let addr = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string());
    let addr = addr
        .to_socket_addrs()
        .expect("Not a valid address")
        .next()
        .expect("Not a socket address");

    let hm: HashMap<SocketAddr, Tx> = HashMap::new();
    let state = PeerMap::new(Mutex::new(hm));

    // Create the event loop and TCP listener we'll accept connections on.
    let try_socket = TcpListener::bind(&addr).await;
    let mut listener = try_socket.expect("Failed to bind");
    info!("Listening on: {}", addr);

    while let Ok((stream, _)) = listener.accept().await {
        tokio::spawn(accept_connection(state.clone(), stream));
    }

    Ok(())
}
<|endoftext|>