<commit_before>use std::collections::HashMap;

use crossfont::Metrics;

use alacritty_terminal::index::{Column, Point};
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{RenderableCell, SizeInfo};

#[derive(Debug, Copy, Clone)]
pub struct RenderRect {
    pub x: f32,
    pub y: f32,
    pub width: f32,
    pub height: f32,
    pub color: Rgb,
    pub alpha: f32,
}

impl RenderRect {
    pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
        RenderRect { x, y, width, height, color, alpha }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RenderLine {
    pub start: Point,
    pub end: Point,
    pub color: Rgb,
}

impl RenderLine {
    pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
        let mut rects = Vec::new();

        let mut start = self.start;
        while start.line < self.end.line {
            let end = Point::new(start.line, size.cols() - 1);
            Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color);
            start = Point::new(start.line + 1, Column(0));
        }
        Self::push_rects(&mut rects, metrics, size, flag, start, self.end, self.color);

        rects
    }

    /// Push all rects required to draw the cell's line.
    fn push_rects(
        rects: &mut Vec<RenderRect>,
        metrics: &Metrics,
        size: &SizeInfo,
        flag: Flags,
        start: Point,
        end: Point,
        color: Rgb,
    ) {
        let (position, thickness) = match flag {
            Flags::DOUBLE_UNDERLINE => {
                // Position underlines so each one has 50% of descent available.
                let top_pos = 0.25 * metrics.descent;
                let bottom_pos = 0.75 * metrics.descent;

                rects.push(Self::create_rect(
                    size,
                    metrics.descent,
                    start,
                    end,
                    top_pos,
                    metrics.underline_thickness,
                    color,
                ));

                (bottom_pos, metrics.underline_thickness)
            },
            Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
            Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
            _ => unimplemented!("Invalid flag for cell line drawing specified"),
        };

        rects.push(Self::create_rect(
            size,
            metrics.descent,
            start,
            end,
            position,
            thickness,
            color,
        ));
    }

    /// Create a line's rect at a position relative to the baseline.
    fn create_rect(
        size: &SizeInfo,
        descent: f32,
        start: Point,
        end: Point,
        position: f32,
        mut thickness: f32,
        color: Rgb,
    ) -> RenderRect {
        let start_x = start.col.0 as f32 * size.cell_width();
        let end_x = (end.col.0 + 1) as f32 * size.cell_width();
        let width = end_x - start_x;

        // Make sure lines are always visible.
        thickness = thickness.max(1.);

        let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height();
        let baseline = line_bottom + descent;

        let mut y = (baseline - position - thickness / 2.).ceil();
        let max_y = line_bottom - thickness;
        if y > max_y {
            y = max_y;
        }

        RenderRect::new(
            start_x + size.padding_x(),
            y + size.padding_y(),
            width,
            thickness,
            color,
            1.,
        )
    }
}

/// Lines for underline and strikeout.
#[derive(Default)]
pub struct RenderLines {
    inner: HashMap<Flags, Vec<RenderLine>>,
}

impl RenderLines {
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    #[inline]
    pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
        self.inner
            .iter()
            .map(|(flag, lines)| -> Vec<RenderRect> {
                lines.iter().map(|line| line.rects(*flag, metrics, size)).flatten().collect()
            })
            .flatten()
            .collect()
    }

    /// Update the stored lines with the next cell info.
    #[inline]
    pub fn update(&mut self, cell: RenderableCell) {
        self.update_flag(cell, Flags::UNDERLINE);
        self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
        self.update_flag(cell, Flags::STRIKEOUT);
    }

    /// Update the lines for a specific flag.
    fn update_flag(&mut self, cell: RenderableCell, flag: Flags) {
        if !cell.flags.contains(flag) {
            return;
        }

        // Check if there's an active line.
        if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) {
            if cell.fg == line.color
                && cell.column == line.end.col + 1
                && cell.line == line.end.line
            {
                // Update the length of the line.
                line.end = cell.into();
                return;
            }
        }

        // Start new line if there currently is none.
        let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
        match self.inner.get_mut(&flag) {
            Some(lines) => lines.push(line),
            None => {
                self.inner.insert(flag, vec![line]);
            },
        }
    }
}
<commit_msg>Remove unneeded collect during line rect creation<commit_after>use std::collections::HashMap;

use crossfont::Metrics;

use alacritty_terminal::index::{Column, Point};
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{RenderableCell, SizeInfo};

#[derive(Debug, Copy, Clone)]
pub struct RenderRect {
    pub x: f32,
    pub y: f32,
    pub width: f32,
    pub height: f32,
    pub color: Rgb,
    pub alpha: f32,
}

impl RenderRect {
    pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
        RenderRect { x, y, width, height, color, alpha }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RenderLine {
    pub start: Point,
    pub end: Point,
    pub color: Rgb,
}

impl RenderLine {
    pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
        let mut rects = Vec::new();

        let mut start = self.start;
        while start.line < self.end.line {
            let end = Point::new(start.line, size.cols() - 1);
            Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color);
            start = Point::new(start.line + 1, Column(0));
        }
        Self::push_rects(&mut rects, metrics, size, flag, start, self.end, self.color);

        rects
    }

    /// Push all rects required to draw the cell's line.
    fn push_rects(
        rects: &mut Vec<RenderRect>,
        metrics: &Metrics,
        size: &SizeInfo,
        flag: Flags,
        start: Point,
        end: Point,
        color: Rgb,
    ) {
        let (position, thickness) = match flag {
            Flags::DOUBLE_UNDERLINE => {
                // Position underlines so each one has 50% of descent available.
                let top_pos = 0.25 * metrics.descent;
                let bottom_pos = 0.75 * metrics.descent;

                rects.push(Self::create_rect(
                    size,
                    metrics.descent,
                    start,
                    end,
                    top_pos,
                    metrics.underline_thickness,
                    color,
                ));

                (bottom_pos, metrics.underline_thickness)
            },
            Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
            Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
            _ => unimplemented!("Invalid flag for cell line drawing specified"),
        };

        rects.push(Self::create_rect(
            size,
            metrics.descent,
            start,
            end,
            position,
            thickness,
            color,
        ));
    }

    /// Create a line's rect at a position relative to the baseline.
    fn create_rect(
        size: &SizeInfo,
        descent: f32,
        start: Point,
        end: Point,
        position: f32,
        mut thickness: f32,
        color: Rgb,
    ) -> RenderRect {
        let start_x = start.col.0 as f32 * size.cell_width();
        let end_x = (end.col.0 + 1) as f32 * size.cell_width();
        let width = end_x - start_x;

        // Make sure lines are always visible.
        thickness = thickness.max(1.);

        let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height();
        let baseline = line_bottom + descent;

        let mut y = (baseline - position - thickness / 2.).ceil();
        let max_y = line_bottom - thickness;
        if y > max_y {
            y = max_y;
        }

        RenderRect::new(
            start_x + size.padding_x(),
            y + size.padding_y(),
            width,
            thickness,
            color,
            1.,
        )
    }
}

/// Lines for underline and strikeout.
#[derive(Default)]
pub struct RenderLines {
    inner: HashMap<Flags, Vec<RenderLine>>,
}

impl RenderLines {
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    #[inline]
    pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
        self.inner
            .iter()
            .flat_map(|(flag, lines)| {
                lines.iter().flat_map(move |line| line.rects(*flag, metrics, size))
            })
            .collect()
    }

    /// Update the stored lines with the next cell info.
    #[inline]
    pub fn update(&mut self, cell: RenderableCell) {
        self.update_flag(cell, Flags::UNDERLINE);
        self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
        self.update_flag(cell, Flags::STRIKEOUT);
    }

    /// Update the lines for a specific flag.
    fn update_flag(&mut self, cell: RenderableCell, flag: Flags) {
        if !cell.flags.contains(flag) {
            return;
        }

        // Check if there's an active line.
        if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) {
            if cell.fg == line.color
                && cell.column == line.end.col + 1
                && cell.line == line.end.line
            {
                // Update the length of the line.
                line.end = cell.into();
                return;
            }
        }

        // Start new line if there currently is none.
        let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
        match self.inner.get_mut(&flag) {
            Some(lines) => lines.push(line),
            None => {
                self.inner.insert(flag, vec![line]);
            },
        }
    }
}
<|endoftext|>


<commit_before>use byteorder::{self, ByteOrder};
use std::collections::HashMap;
use std::mem;
use std::ptr;

use interpreter::{EvalError, EvalResult};

pub struct Memory {
    next_id: u64,
    alloc_map: HashMap<u64, Allocation>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AllocId(u64);

#[derive(Debug)]
pub struct Allocation {
    pub bytes: Vec<u8>,
    // TODO(tsion): relocations
    // TODO(tsion): undef mask
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Pointer {
    pub alloc_id: AllocId,
    pub offset: usize,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldRepr {
    pub offset: usize,
    pub repr: Repr,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Repr {
    Bool,
    Int,
    Aggregate {
        size: usize,
        fields: Vec<FieldRepr>,
    },
}

impl Memory {
    pub fn new() -> Self {
        Memory { next_id: 0, alloc_map: HashMap::new() }
    }

    pub fn allocate(&mut self, size: usize) -> Pointer {
        let id = AllocId(self.next_id);
        let alloc = Allocation { bytes: vec![0; size] };
        self.alloc_map.insert(self.next_id, alloc);
        self.next_id += 1;
        Pointer {
            alloc_id: id,
            offset: 0,
        }
    }

    pub fn get(&self, id: AllocId) -> EvalResult<&Allocation> {
        self.alloc_map.get(&id.0).ok_or(EvalError::DanglingPointerDeref)
    }

    pub fn get_mut(&mut self, id: AllocId) -> EvalResult<&mut Allocation> {
        self.alloc_map.get_mut(&id.0).ok_or(EvalError::DanglingPointerDeref)
    }

    fn get_bytes(&self, ptr: Pointer, size: usize) -> EvalResult<&[u8]> {
        let alloc = try!(self.get(ptr.alloc_id));
        try!(alloc.check_bytes(ptr.offset, ptr.offset + size));
        Ok(&alloc.bytes[ptr.offset..ptr.offset + size])
    }

    fn get_bytes_mut(&mut self, ptr: Pointer, size: usize) -> EvalResult<&mut [u8]> {
        let alloc = try!(self.get_mut(ptr.alloc_id));
        try!(alloc.check_bytes(ptr.offset, ptr.offset + size));
        Ok(&mut alloc.bytes[ptr.offset..ptr.offset + size])
    }

    pub fn copy(&mut self, src: Pointer, dest: Pointer, size: usize) -> EvalResult<()> {
        let src_bytes = try!(self.get_bytes_mut(src, size)).as_mut_ptr();
        let dest_bytes = try!(self.get_bytes_mut(dest, size)).as_mut_ptr();

        // SAFE: The above indexing would have panicked if there weren't at least `size` bytes
        // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and
        // `dest` could possibly overlap.
        unsafe {
            if src.alloc_id == dest.alloc_id {
                ptr::copy(src_bytes, dest_bytes, size);
            } else {
                ptr::copy_nonoverlapping(src_bytes, dest_bytes, size);
            }
        }

        Ok(())
    }

    pub fn read_int(&self, ptr: Pointer) -> EvalResult<i64> {
        self.get_bytes(ptr, Repr::Int.size()).map(byteorder::NativeEndian::read_i64)
    }

    pub fn write_int(&mut self, ptr: Pointer, n: i64) -> EvalResult<()> {
        let bytes = try!(self.get_bytes_mut(ptr, Repr::Int.size()));
        byteorder::NativeEndian::write_i64(bytes, n);
        Ok(())
    }

    pub fn read_bool(&self, ptr: Pointer) -> EvalResult<bool> {
        let bytes = try!(self.get_bytes(ptr, 1));
        match bytes[0] {
            0 => Ok(false),
            1 => Ok(true),
            _ => Err(EvalError::InvalidBool),
        }
    }

    pub fn write_bool(&mut self, ptr: Pointer, b: bool) -> EvalResult<()> {
        let bytes = try!(self.get_bytes_mut(ptr, 1));
        bytes[0] = b as u8;
        Ok(())
    }
}

impl Allocation {
    fn check_bytes(&self, start: usize, end: usize) -> EvalResult<()> {
        if start < self.bytes.len() && end <= self.bytes.len() {
            Ok(())
        } else {
            Err(EvalError::PointerOutOfBounds)
        }
    }
}

impl Pointer {
    pub fn offset(self, i: usize) -> Self {
        // TODO(tsion): Check for offset out of bounds.
        Pointer { offset: self.offset + i, ..self }
    }
}

impl Repr {
    pub fn size(&self) -> usize {
        match *self {
            Repr::Bool => 1,
            Repr::Int => mem::size_of::<i64>(),
            Repr::Aggregate { size, .. } => size,
        }
    }
}
<commit_msg>Fix 0-sized writes being treated as out of bounds.<commit_after>use byteorder::{self, ByteOrder};
use std::collections::HashMap;
use std::mem;
use std::ptr;

use interpreter::{EvalError, EvalResult};

pub struct Memory {
    next_id: u64,
    alloc_map: HashMap<u64, Allocation>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AllocId(u64);

#[derive(Debug)]
pub struct Allocation {
    pub bytes: Vec<u8>,
    // TODO(tsion): relocations
    // TODO(tsion): undef mask
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Pointer {
    pub alloc_id: AllocId,
    pub offset: usize,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldRepr {
    pub offset: usize,
    pub repr: Repr,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Repr {
    Bool,
    Int,
    Aggregate {
        size: usize,
        fields: Vec<FieldRepr>,
    },
}

impl Memory {
    pub fn new() -> Self {
        Memory { next_id: 0, alloc_map: HashMap::new() }
    }

    pub fn allocate(&mut self, size: usize) -> Pointer {
        let id = AllocId(self.next_id);
        let alloc = Allocation { bytes: vec![0; size] };
        self.alloc_map.insert(self.next_id, alloc);
        self.next_id += 1;
        Pointer {
            alloc_id: id,
            offset: 0,
        }
    }

    pub fn get(&self, id: AllocId) -> EvalResult<&Allocation> {
        self.alloc_map.get(&id.0).ok_or(EvalError::DanglingPointerDeref)
    }

    pub fn get_mut(&mut self, id: AllocId) -> EvalResult<&mut Allocation> {
        self.alloc_map.get_mut(&id.0).ok_or(EvalError::DanglingPointerDeref)
    }

    fn get_bytes(&self, ptr: Pointer, size: usize) -> EvalResult<&[u8]> {
        let alloc = try!(self.get(ptr.alloc_id));
        try!(alloc.check_bytes(ptr.offset, ptr.offset + size));
        Ok(&alloc.bytes[ptr.offset..ptr.offset + size])
    }

    fn get_bytes_mut(&mut self, ptr: Pointer, size: usize) -> EvalResult<&mut [u8]> {
        let alloc = try!(self.get_mut(ptr.alloc_id));
        try!(alloc.check_bytes(ptr.offset, ptr.offset + size));
        Ok(&mut alloc.bytes[ptr.offset..ptr.offset + size])
    }

    pub fn copy(&mut self, src: Pointer, dest: Pointer, size: usize) -> EvalResult<()> {
        let src_bytes = try!(self.get_bytes_mut(src, size)).as_mut_ptr();
        let dest_bytes = try!(self.get_bytes_mut(dest, size)).as_mut_ptr();

        // SAFE: The above indexing would have panicked if there weren't at least `size` bytes
        // behind `src` and `dest`. Also, we use the overlapping-safe `ptr::copy` if `src` and
        // `dest` could possibly overlap.
        unsafe {
            if src.alloc_id == dest.alloc_id {
                ptr::copy(src_bytes, dest_bytes, size);
            } else {
                ptr::copy_nonoverlapping(src_bytes, dest_bytes, size);
            }
        }

        Ok(())
    }

    pub fn read_int(&self, ptr: Pointer) -> EvalResult<i64> {
        self.get_bytes(ptr, Repr::Int.size()).map(byteorder::NativeEndian::read_i64)
    }

    pub fn write_int(&mut self, ptr: Pointer, n: i64) -> EvalResult<()> {
        let bytes = try!(self.get_bytes_mut(ptr, Repr::Int.size()));
        byteorder::NativeEndian::write_i64(bytes, n);
        Ok(())
    }

    pub fn read_bool(&self, ptr: Pointer) -> EvalResult<bool> {
        let bytes = try!(self.get_bytes(ptr, 1));
        match bytes[0] {
            0 => Ok(false),
            1 => Ok(true),
            _ => Err(EvalError::InvalidBool),
        }
    }

    pub fn write_bool(&mut self, ptr: Pointer, b: bool) -> EvalResult<()> {
        let bytes = try!(self.get_bytes_mut(ptr, 1));
        bytes[0] = b as u8;
        Ok(())
    }
}

impl Allocation {
    fn check_bytes(&self, start: usize, end: usize) -> EvalResult<()> {
        if start <= self.bytes.len() && end <= self.bytes.len() {
            Ok(())
        } else {
            Err(EvalError::PointerOutOfBounds)
        }
    }
}

impl Pointer {
    pub fn offset(self, i: usize) -> Self {
        // TODO(tsion): Check for offset out of bounds.
        Pointer { offset: self.offset + i, ..self }
    }
}

impl Repr {
    pub fn size(&self) -> usize {
        match *self {
            Repr::Bool => 1,
            Repr::Int => mem::size_of::<i64>(),
            Repr::Aggregate { size, .. } => size,
        }
    }
}
<|endoftext|>


<commit_before>/// Internal metric representation
///
use std::fmt;
use std::str::FromStr;
use std::collections::HashMap;

/// Enum of metric types
pub enum MetricKind {
    Counter(f64), // sample rate
    Gauge,
    Timer,
    Histogram
}

impl fmt::Debug for MetricKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MetricKind::Gauge      => write!(f, "Gauge"),
            MetricKind::Timer      => write!(f, "Timer"),
            MetricKind::Histogram  => write!(f, "Histogram"),
            MetricKind::Counter(s) => write!(f, "Counter(s={})", s)
        }
    }
}

/// Error types for parsing Metrics from strings.
///
pub enum ParseError {
    // Error message, column
    SyntaxError(&'static str, usize)
}

/// Metric value objects.
///
#[derive(Debug)]
pub struct Metric {
    kind: MetricKind,
    name: String,
    value: f64
}

impl Metric {
    fn new(name: &str, value: f64, kind: MetricKind) -> Metric {
        Metric{name: name.to_string(), value: value, kind: kind}
    }
}

impl FromStr for Metric {
    type Err = ParseError;

    /// Valid message formats are:
    ///
    /// - `<str:metric_name>:<f64:value>|<str:type>`
    /// - `<str:metric_name>:<f64:value>|c|@<f64:sample_rate>`
    fn from_str(line: &str) -> Result<Metric, ParseError> {
        // Get the metric name
        let name_parts: Vec<&str> = line.split(':').collect();
        if name_parts.len() < 2 || name_parts[0].is_empty() {
            return Err(ParseError::SyntaxError(
                    "Metrics require a name.",
                    0))
        }
        let name = name_parts[0].to_string();

        // Get the float val
        let val_parts: Vec<&str> = name_parts[1].split('|').collect();
        if val_parts.len() < 2 || val_parts[0].is_empty() {
            return Err(ParseError::SyntaxError(
                    "Metrics require a value.",
                    name.len()))
        }
        let value = val_parts[0].parse::<f64>().ok().unwrap();

        // Get kind parts
        let kind = match val_parts[1] {
            "ms" => MetricKind::Timer,
            "g" => MetricKind::Gauge,
            "h" => MetricKind::Histogram,
            "c" => {
                let mut rate:f64 = 1.0;
                if val_parts.len() == 3 {
                    rate = val_parts[2].trim_left_matches('@')
                        .parse::<f64>().ok().unwrap();
                }
                MetricKind::Counter(rate)
            }
            _ => return Err(ParseError::SyntaxError(
                    "Unknown metric type.",
                    2))
        };

        Ok(Metric{name: name, value: value, kind: kind})
    }
}

//
// Tests
//
#[test]
fn test_metric_kind_debug_fmt() {
    assert_eq!(
        "Gauge",
        format!("{:?}", MetricKind::Gauge)
    );
    assert_eq!(
        "Timer",
        format!("{:?}", MetricKind::Timer)
    );
    assert_eq!(
        "Histogram",
        format!("{:?}", MetricKind::Histogram)
    );
    assert_eq!(
        "Counter(s=6)",
        format!("{:?}", MetricKind::Counter(6.0))
    );
}

#[test]
fn test_metric_from_str_invalid_no_name() {
    let res = Metric::from_str("");
    assert!(res.is_err(), "Should have an error");
    assert!(!res.is_ok(), "Should have an error");
}

#[test]
fn test_metric_from_str_invalid_no_value() {
    let res = Metric::from_str("foo:");
    assert!(res.is_err(), "Should have an error");
    assert!(!res.is_ok(), "Should have an error");
}

#[test]
fn test_metric_valid() {
    let mut valid = HashMap::new();
    valid.insert(
        "foo.test:12.3|ms",
        Metric::new("foo.test", 12.3, MetricKind::Timer)
    );
    valid.insert(
        "test:18.123|g",
        Metric::new("test", 18.123, MetricKind::Gauge)
    );
    valid.insert(
        "test:18.123|g",
        Metric::new("test", 18.123, MetricKind::Gauge)
    );
    valid.insert(
        "foo.bar.val:18.123|h",
        Metric::new("foo.bar.val", 18.123, MetricKind::Histogram)
    );
    valid.insert(
        "thing.total:12|c",
        Metric::new("thing.total", 12.0, MetricKind::Counter(1.0))
    );
    valid.insert(
        "thing.total:5.6|c|@123",
        Metric::new("thing.total", 5.6, MetricKind::Counter(123.0))
    );

    for (input, expected) in valid.iter() {
        let result = Metric::from_str(*input);
        assert!(result.is_ok());

        let actual = result.ok().unwrap();
        assert_eq!(expected.name, actual.name);
        assert_eq!(expected.value, actual.value);

        // TODO this is stupid, there must be a better way.
        assert_eq!(
            format!("{:?}", expected.kind),
            format!("{:?}", actual.kind)
        );
    }
}

#[test]
fn test_metric_invalid() {
    let invalid = vec![
        "",
        "metric",
        "metric|12",
        "metric:13|",
        "metric:14|c@1",
        ":|@",
        ":1.0|c"
    ];
    for input in invalid.iter() {
        let result = Metric::from_str(*input);
        assert!(result.is_err());
    }
}
<commit_msg>Fix unused import warning.<commit_after>/// Internal metric representation
///
use std::fmt;
use std::str::FromStr;

/// Enum of metric types
pub enum MetricKind {
    Counter(f64), // sample rate
    Gauge,
    Timer,
}

impl fmt::Debug for MetricKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MetricKind::Gauge      => write!(f, "Gauge"),
            MetricKind::Timer      => write!(f, "Timer"),
            MetricKind::Counter(s) => write!(f, "Counter(s={})", s)
        }
    }
}

/// Error types for parsing Metrics from strings.
///
pub enum ParseError {
    // Error message, column
    SyntaxError(&'static str, usize)
}

/// Metric value objects.
///
#[derive(Debug)]
pub struct Metric {
    kind: MetricKind,
    name: String,
    value: f64
}

impl Metric {
    fn new(name: &str, value: f64, kind: MetricKind) -> Metric {
        Metric{name: name.to_string(), value: value, kind: kind}
    }
}

impl FromStr for Metric {
    type Err = ParseError;

    /// Valid message formats are:
    ///
    /// - `<str:metric_name>:<f64:value>|<str:type>`
    /// - `<str:metric_name>:<f64:value>|c|@<f64:sample_rate>`
    fn from_str(line: &str) -> Result<Metric, ParseError> {
        // Get the metric name
        let name_parts: Vec<&str> = line.split(':').collect();
        if name_parts.len() < 2 || name_parts[0].is_empty() {
            return Err(ParseError::SyntaxError(
                    "Metrics require a name.",
                    0))
        }
        let name = name_parts[0].to_string();

        // Get the float val
        let val_parts: Vec<&str> = name_parts[1].split('|').collect();
        if val_parts.len() < 2 || val_parts[0].is_empty() {
            return Err(ParseError::SyntaxError(
                    "Metrics require a value.",
                    name.len()))
        }
        let value = val_parts[0].parse::<f64>().ok().unwrap();

        // Get kind parts
        let kind = match val_parts[1] {
            "ms" => MetricKind::Timer,
            "g" => MetricKind::Gauge,
            "c" => {
                let mut rate:f64 = 1.0;
                if val_parts.len() == 3 {
                    rate = val_parts[2].trim_left_matches('@')
                        .parse::<f64>().ok().unwrap();
                }
                MetricKind::Counter(rate)
            }
            _ => return Err(ParseError::SyntaxError(
                    "Unknown metric type.",
                    2))
        };

        Ok(Metric{name: name, value: value, kind: kind})
    }
}

//
// Tests
//
#[cfg(test)]
mod test {
    use metric::{Metric,MetricKind};
    use std::str::FromStr;
    use std::collections::HashMap;

    #[test]
    fn test_metric_kind_debug_fmt() {
        assert_eq!(
            "Gauge",
            format!("{:?}", MetricKind::Gauge)
        );
        assert_eq!(
            "Timer",
            format!("{:?}", MetricKind::Timer)
        );
        assert_eq!(
            "Counter(s=6)",
            format!("{:?}", MetricKind::Counter(6.0))
        );
    }

    #[test]
    fn test_metric_from_str_invalid_no_name() {
        let res = Metric::from_str("");
        assert!(res.is_err(), "Should have an error");
        assert!(!res.is_ok(), "Should have an error");
    }

    #[test]
    fn test_metric_from_str_invalid_no_value() {
        let res = Metric::from_str("foo:");
        assert!(res.is_err(), "Should have an error");
        assert!(!res.is_ok(), "Should have an error");
    }

    #[test]
    fn test_metric_valid() {
        let mut valid = HashMap::new();
        valid.insert(
            "foo.test:12.3|ms",
            Metric::new("foo.test", 12.3, MetricKind::Timer)
        );
        valid.insert(
            "test:18.123|g",
            Metric::new("test", 18.123, MetricKind::Gauge)
        );
        valid.insert(
            "test:18.123|g",
            Metric::new("test", 18.123, MetricKind::Gauge)
        );
        valid.insert(
            "thing.total:12|c",
            Metric::new("thing.total", 12.0, MetricKind::Counter(1.0))
        );
        valid.insert(
            "thing.total:5.6|c|@123",
            Metric::new("thing.total", 5.6, MetricKind::Counter(123.0))
        );

        for (input, expected) in valid.iter() {
            let result = Metric::from_str(*input);
            assert!(result.is_ok());

            let actual = result.ok().unwrap();
            assert_eq!(expected.name, actual.name);
            assert_eq!(expected.value, actual.value);

            // TODO this is stupid, there must be a better way.
            assert_eq!(
                format!("{:?}", expected.kind),
                format!("{:?}", actual.kind)
            );
        }
    }

    #[test]
    fn test_metric_invalid() {
        let invalid = vec![
            "",
            "metric",
            "metric|12",
            "metric:13|",
            "metric:14|c@1",
            ":|@",
            ":1.0|c"
        ];
        for input in invalid.iter() {
            let result = Metric::from_str(*input);
            assert!(result.is_err());
        }
    }
}
<|endoftext|>


<commit_before>//! Definition of the `JoinAll` combinator, waiting for all of a list of futures
//! to finish.

use core::fmt;
use core::future::Future;
use core::iter::FromIterator;
use core::mem;
use core::pin::Pin;
use core::task::{Context, Poll};
use alloc::boxed::Box;
use alloc::vec::Vec;

use super::MaybeDone;

fn iter_pin_mut<T>(slice: Pin<&mut [T]>) -> impl Iterator<Item = Pin<&mut T>> {
    // Safety: `std` _could_ make this unsound if it were to decide Pin's
    // invariants aren't required to transmit through slices. Otherwise this has
    // the same safety as a normal field pin projection.
    unsafe { slice.get_unchecked_mut() }
        .iter_mut()
        .map(|t| unsafe { Pin::new_unchecked(t) })
}

/// Future for the [`join_all`] function.
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct JoinAll<F>
where
    F: Future,
{
    elems: Pin<Box<[MaybeDone<F>]>>,
}

impl<F> fmt::Debug for JoinAll<F>
where
    F: Future + fmt::Debug,
    F::Output: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("JoinAll")
            .field("elems", &self.elems)
            .finish()
    }
}

/// Creates a future which represents a collection of the outputs of the futures
/// given.
///
/// The returned future will drive execution for all of its underlying futures,
/// collecting the results into a destination `Vec<T>` in the same order as they
/// were provided.
///
/// This function is only available when the `std` or `alloc` feature of this
/// library is activated, and it is activated by default.
///
/// # See Also
///
/// This is purposefully a very simple API for basic use-cases. In a lot of
/// cases you will want to use the more powerful
/// [`FuturesUnordered`][crate::stream::FuturesUnordered] APIs, some
/// examples of additional functionality that provides:
///
///  * Adding new futures to the set even after it has been started.
///
///  * Only polling the specific futures that have been woken. In cases where
///    you have a lot of futures this will result in much more efficient polling.
///
/// # Examples
///
/// ```
/// # futures::executor::block_on(async {
/// use futures::future::join_all;
///
/// async fn foo(i: u32) -> u32 { i }
///
/// let futures = vec![foo(1), foo(2), foo(3)];
///
/// assert_eq!(join_all(futures).await, [1, 2, 3]);
/// # });
/// ```
pub fn join_all<I>(i: I) -> JoinAll<I::Item>
where
    I: IntoIterator,
    I::Item: Future,
{
    let elems: Box<[_]> = i.into_iter().map(MaybeDone::Future).collect();
    JoinAll { elems: elems.into() }
}

impl<F> Future for JoinAll<F>
where
    F: Future,
{
    type Output = Vec<F::Output>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut all_done = true;

        for elem in iter_pin_mut(self.elems.as_mut()) {
            if elem.poll(cx).is_pending() {
                all_done = false;
            }
        }

        if all_done {
            let mut elems = mem::replace(&mut self.elems, Box::pin([]));
            let result = iter_pin_mut(elems.as_mut())
                .map(|e| e.take_output().unwrap())
                .collect();
            Poll::Ready(result)
        } else {
            Poll::Pending
        }
    }
}

impl<F: Future> FromIterator<F> for JoinAll<F> {
    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
        join_all(iter)
    }
}
<commit_msg>Mention FuturesOrdered as alternative to join_all (#2190)<commit_after>//! Definition of the `JoinAll` combinator, waiting for all of a list of futures
//! to finish.

use core::fmt;
use core::future::Future;
use core::iter::FromIterator;
use core::mem;
use core::pin::Pin;
use core::task::{Context, Poll};
use alloc::boxed::Box;
use alloc::vec::Vec;

use super::MaybeDone;

fn iter_pin_mut<T>(slice: Pin<&mut [T]>) -> impl Iterator<Item = Pin<&mut T>> {
    // Safety: `std` _could_ make this unsound if it were to decide Pin's
    // invariants aren't required to transmit through slices. Otherwise this has
    // the same safety as a normal field pin projection.
    unsafe { slice.get_unchecked_mut() }
        .iter_mut()
        .map(|t| unsafe { Pin::new_unchecked(t) })
}

/// Future for the [`join_all`] function.
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct JoinAll<F>
where
    F: Future,
{
    elems: Pin<Box<[MaybeDone<F>]>>,
}

impl<F> fmt::Debug for JoinAll<F>
where
    F: Future + fmt::Debug,
    F::Output: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("JoinAll")
            .field("elems", &self.elems)
            .finish()
    }
}

/// Creates a future which represents a collection of the outputs of the futures
/// given.
///
/// The returned future will drive execution for all of its underlying futures,
/// collecting the results into a destination `Vec<T>` in the same order as they
/// were provided.
///
/// This function is only available when the `std` or `alloc` feature of this
/// library is activated, and it is activated by default.
///
/// # See Also
///
/// This is purposefully a very simple API for basic use-cases. In a lot of
/// cases you will want to use the more powerful
/// [`FuturesOrdered`][crate::stream::FuturesOrdered] APIs, or, if order does
/// not matter, [`FuturesUnordered`][crate::stream::FuturesUnordered].
///
/// Some examples for additional functionality provided by these are:
///
///  * Adding new futures to the set even after it has been started.
///
///  * Only polling the specific futures that have been woken. In cases where
///    you have a lot of futures this will result in much more efficient polling.
///
/// # Examples
///
/// ```
/// # futures::executor::block_on(async {
/// use futures::future::join_all;
///
/// async fn foo(i: u32) -> u32 { i }
///
/// let futures = vec![foo(1), foo(2), foo(3)];
///
/// assert_eq!(join_all(futures).await, [1, 2, 3]);
/// # });
/// ```
pub fn join_all<I>(i: I) -> JoinAll<I::Item>
where
    I: IntoIterator,
    I::Item: Future,
{
    let elems: Box<[_]> = i.into_iter().map(MaybeDone::Future).collect();
    JoinAll { elems: elems.into() }
}

impl<F> Future for JoinAll<F>
where
    F: Future,
{
    type Output = Vec<F::Output>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut all_done = true;

        for elem in iter_pin_mut(self.elems.as_mut()) {
            if elem.poll(cx).is_pending() {
                all_done = false;
            }
        }

        if all_done {
            let mut elems = mem::replace(&mut self.elems, Box::pin([]));
            let result = iter_pin_mut(elems.as_mut())
                .map(|e| e.take_output().unwrap())
                .collect();
            Poll::Ready(result)
        } else {
            Poll::Pending
        }
    }
}

impl<F: Future> FromIterator<F> for JoinAll<F> {
    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
        join_all(iter)
    }
}
<|endoftext|>


<commit_before>#![recursion_limit="128"]
#![feature(async_await)]

use futures::{Poll, pending, pin_mut, poll, join, try_join, select};
use futures::channel::{mpsc, oneshot};
use futures::executor::block_on;
use futures::future::{self, FutureExt};
use futures::stream::StreamExt;
use futures::sink::SinkExt;

#[test]
fn poll_and_pending() {
    let pending_once = async { pending!() };
    block_on(async {
        pin_mut!(pending_once);
        assert_eq!(Poll::Pending, poll!(&mut pending_once));
        assert_eq!(Poll::Ready(()), poll!(&mut pending_once));
    });
}

#[test]
fn join() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();

    let fut = async {
        let res = join!(rx1, rx2);
        assert_eq!((Ok(1), Ok(2)), res);
    };

    block_on(async {
        pin_mut!(fut);
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx1.send(1).unwrap();
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx2.send(2).unwrap();
        assert_eq!(Poll::Ready(()), poll!(&mut fut));
    });
}

#[test]
fn select() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (_tx2, rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            res = rx1.fuse() => {
                assert_eq!(Ok(1), res);
                ran = true;
            },
            _ = rx2.fuse() => unreachable!(),
        }
    });
    assert!(ran);
}

#[test]
fn select_streams() {
    let (mut tx1, rx1) = mpsc::channel::<i32>(1);
    let (mut tx2, rx2) = mpsc::channel::<i32>(1);
    let mut rx1 = rx1.fuse();
    let mut rx2 = rx2.fuse();
    let mut ran = false;
    let mut total = 0;
    block_on(async {
        let mut tx1_opt;
        let mut tx2_opt;
        select! {
            _ = rx1.next() => panic!(),
            _ = rx2.next() => panic!(),
            default => {
                tx1.send(2).await.unwrap();
                tx2.send(3).await.unwrap();
                tx1_opt = Some(tx1);
                tx2_opt = Some(tx2);
            }
            complete => panic!(),
        }
        loop {
            select! {
                // runs first and again after default
                x = rx1.next() => if let Some(x) = x { total += x; },
                // runs second and again after default
                x = rx2.next()  => if let Some(x) = x { total += x; },
                // runs third
                default => {
                    assert_eq!(total, 5);
                    ran = true;
                    drop(tx1_opt.take().unwrap());
                    drop(tx2_opt.take().unwrap());
                },
                // runs last
                complete => break,
            };
        }
    });
    assert!(ran);
}

#[test]
fn select_can_move_uncompleted_futures() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    tx2.send(2).unwrap();
    let mut ran = false;
    let mut rx1 = rx1.fuse();
    let mut rx2 = rx2.fuse();
    block_on(async {
        select! {
            res = rx1 => {
                assert_eq!(Ok(1), res);
                assert_eq!(Ok(2), rx2.await);
                ran = true;
            },
            res = rx2 => {
                assert_eq!(Ok(2), res);
                assert_eq!(Ok(1), rx1.await);
                ran = true;
            },
        }
    });
    assert!(ran);
}

#[test]
fn select_nested() {
    let mut outer_fut = future::ready(1);
    let mut inner_fut = future::ready(2);
    let res = block_on(async {
        select! {
            x = outer_fut => {
                select! {
                    y = inner_fut => x + y,
                }
            }
        }
    });
    assert_eq!(res, 3);
}

#[test]
fn select_size() {
    let fut = async {
        let mut ready = future::ready(0i32);
        select! {
            _ = ready => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 24);

    let fut = async {
        let mut ready1 = future::ready(0i32);
        let mut ready2 = future::ready(0i32);
        select! {
            _ = ready1 => {},
            _ = ready2 => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 40);
}

#[test]
fn join_size() {
    let fut = async {
        let ready = future::ready(0i32);
        join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 16);

    let fut = async {
        let ready1 = future::ready(0i32);
        let ready2 = future::ready(0i32);
        join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 44);
}

#[test]
fn try_join_size() {
    let fut = async {
        let ready = future::ready(Ok::<i32, i32>(0));
        try_join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 16);

    let fut = async {
        let ready1 = future::ready(Ok::<i32, i32>(0));
        let ready2 = future::ready(Ok::<i32, i32>(0));
        try_join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 44);
}

#[test]
fn join_doesnt_require_unpin() {
    let _ = async {
        let x = async {};
        let y = async {};
        join!(x, y)
    };
}

#[test]
fn try_join_doesnt_require_unpin() {
    let _ = async {
        let x = async { Ok::<(), ()>(()) };
        let y = async { Ok::<(), ()>(()) };
        try_join!(x, y)
    };
}
<commit_msg>Update asserted size of join! futures<commit_after>#![recursion_limit="128"]
#![feature(async_await)]

use futures::{Poll, pending, pin_mut, poll, join, try_join, select};
use futures::channel::{mpsc, oneshot};
use futures::executor::block_on;
use futures::future::{self, FutureExt};
use futures::stream::StreamExt;
use futures::sink::SinkExt;

#[test]
fn poll_and_pending() {
    let pending_once = async { pending!() };
    block_on(async {
        pin_mut!(pending_once);
        assert_eq!(Poll::Pending, poll!(&mut pending_once));
        assert_eq!(Poll::Ready(()), poll!(&mut pending_once));
    });
}

#[test]
fn join() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();

    let fut = async {
        let res = join!(rx1, rx2);
        assert_eq!((Ok(1), Ok(2)), res);
    };

    block_on(async {
        pin_mut!(fut);
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx1.send(1).unwrap();
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx2.send(2).unwrap();
        assert_eq!(Poll::Ready(()), poll!(&mut fut));
    });
}

#[test]
fn select() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (_tx2, rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            res = rx1.fuse() => {
                assert_eq!(Ok(1), res);
                ran = true;
            },
            _ = rx2.fuse() => unreachable!(),
        }
    });
    assert!(ran);
}

#[test]
fn select_streams() {
    let (mut tx1, rx1) = mpsc::channel::<i32>(1);
    let (mut tx2, rx2) = mpsc::channel::<i32>(1);
    let mut rx1 = rx1.fuse();
    let mut rx2 = rx2.fuse();
    let mut ran = false;
    let mut total = 0;
    block_on(async {
        let mut tx1_opt;
        let mut tx2_opt;
        select! {
            _ = rx1.next() => panic!(),
            _ = rx2.next() => panic!(),
            default => {
                tx1.send(2).await.unwrap();
                tx2.send(3).await.unwrap();
                tx1_opt = Some(tx1);
                tx2_opt = Some(tx2);
            }
            complete => panic!(),
        }
        loop {
            select! {
                // runs first and again after default
                x = rx1.next() => if let Some(x) = x { total += x; },
                // runs second and again after default
                x = rx2.next()  => if let Some(x) = x { total += x; },
                // runs third
                default => {
                    assert_eq!(total, 5);
                    ran = true;
                    drop(tx1_opt.take().unwrap());
                    drop(tx2_opt.take().unwrap());
                },
                // runs last
                complete => break,
            };
        }
    });
    assert!(ran);
}

#[test]
fn select_can_move_uncompleted_futures() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    tx2.send(2).unwrap();
    let mut ran = false;
    let mut rx1 = rx1.fuse();
    let mut rx2 = rx2.fuse();
    block_on(async {
        select! {
            res = rx1 => {
                assert_eq!(Ok(1), res);
                assert_eq!(Ok(2), rx2.await);
                ran = true;
            },
            res = rx2 => {
                assert_eq!(Ok(2), res);
                assert_eq!(Ok(1), rx1.await);
                ran = true;
            },
        }
    });
    assert!(ran);
}

#[test]
fn select_nested() {
    let mut outer_fut = future::ready(1);
    let mut inner_fut = future::ready(2);
    let res = block_on(async {
        select! {
            x = outer_fut => {
                select! {
                    y = inner_fut => x + y,
                }
            }
        }
    });
    assert_eq!(res, 3);
}

#[test]
fn select_size() {
    let fut = async {
        let mut ready = future::ready(0i32);
        select! {
            _ = ready => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 24);

    let fut = async {
        let mut ready1 = future::ready(0i32);
        let mut ready2 = future::ready(0i32);
        select! {
            _ = ready1 => {},
            _ = ready2 => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 40);
}

#[test]
fn join_size() {
    let fut = async {
        let ready = future::ready(0i32);
        join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 16);

    let fut = async {
        let ready1 = future::ready(0i32);
        let ready2 = future::ready(0i32);
        join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 32);
}

#[test]
fn try_join_size() {
    let fut = async {
        let ready = future::ready(Ok::<i32, i32>(0));
        try_join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 16);

    let fut = async {
        let ready1 = future::ready(Ok::<i32, i32>(0));
        let ready2 = future::ready(Ok::<i32, i32>(0));
        try_join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 32);
}

#[test]
fn join_doesnt_require_unpin() {
    let _ = async {
        let x = async {};
        let y = async {};
        join!(x, y)
    };
}

#[test]
fn try_join_doesnt_require_unpin() {
    let _ = async {
        let x = async { Ok::<(), ()>(()) };
        let y = async { Ok::<(), ()>(()) };
        try_join!(x, y)
    };
}
<|endoftext|>


<commit_before>use super::semigroup::Semigroup;
use std::collections::*;
use std::hash::Hash;

pub trait Monoid {
    fn empty() -> Self;
}

/// Return this combined with itself `n` times.
pub fn combine_n<T>(o: &T, times: u32) -> T
    where T: Monoid + Semigroup + Clone
{
    if times == 0 {
        <T as Monoid>::empty()
    } else {
        super::semigroup::combine_n(o, times)
    }
}

/// Given a sequence of `xs`, combine them and return the total
///
/// ```
/// # use frust::monoid::*;
///
/// assert_eq!(combine_all(&vec![Some(1), Some(3)]), Some(4));
///
/// let empty_vec_opt_int:  Vec<Option<i32>> = Vec::new();
/// assert_eq!(combine_all(&empty_vec_opt_int), None);
///
/// let vec_of_some_strings = vec![Some("Hello".to_owned()), Some(" World".to_owned())];
/// assert_eq!(combine_all(&vec_of_some_strings), Some("Hello World".to_owned()));
/// ```
pub fn combine_all<T>(xs: &Vec<T>) -> T
    where T: Monoid + Semigroup + Clone
{
    let mut r = <T as Monoid>::empty();
    for i in xs {
        r = r.combine(i);
    }
    r
}

impl<T> Monoid for Option<T>
    where T: Semigroup + Clone
{
    fn empty() -> Self {
        None
    }
}

impl Monoid for String {
    fn empty() -> Self {
        String::new()
    }
}

impl<T> Monoid for Vec<T> {
    fn empty() -> Self {
        Vec::new()
    }
}

impl<T> Monoid for HashSet<T>
    where T: Hash + Eq
{
    fn empty() -> Self {
        HashSet::new()
    }
}

impl<K, V> Monoid for HashMap<K, V>
    where K: Eq + Hash + Clone,
          V: Semigroup + Clone
{
    fn empty() -> Self {
        HashMap::new()
    }
}

macro_rules! numeric_monoid_imps {
  ($($zero: expr; $tr:ty),*) => {
    $(
      impl Monoid for $tr {
        fn empty() -> Self { $zero }
      }
    )*
  }
}

numeric_monoid_imps! {
    0; i8,
    0; i16,
    0; i32,
    0; i64,
    0; u8,
    0; u16,
    0; u32,
    0; u64,
    0; isize,
    0; usize,
    0f32; f32,
    0f64; f64
}

macro_rules! tuple_impls {
    () => {}; // no more

    (($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
// Invoke recursive reversal of list that ends in the macro expansion implementation
// of the reversed list
//
        tuple_impls!([($idx, $typ);] $( ($nidx => $ntyp), )*);
        tuple_impls!($( ($nidx => $ntyp), )*); // invoke macro on tail
    };

// ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse
// + is empty (see next pattern)
//
    ([$(($accIdx: tt, $accTyp: ident);)+]  ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
      tuple_impls!([($idx, $typ); $(($accIdx, $accTyp); )*] $( ($nidx => $ntyp), ) *);
    };

// Finally expand into our implementation
    ([($idx:tt, $typ:ident); $( ($nidx:tt, $ntyp:ident); )*]) => {
        impl<$typ: Monoid, $( $ntyp: Monoid),*> Monoid for ($typ, $( $ntyp ),*) {
            fn empty() -> Self {
              (<$typ as Monoid>::empty(), $(<$ntyp as Monoid>::empty(), )*)
            }
        }
    }
}

tuple_impls! {
    (20 => U),
    (19 => T),
    (18 => S),
    (17 => R),
    (16 => Q),
    (15 => P),
    (14 => O),
    (13 => N),
    (12 => M),
    (11 => L),
    (10 => K),
    (9 => J),
    (8 => I),
    (7 => H),
    (6 => G),
    (5 => F),
    (4 => E),
    (3 => D),
    (2 => C),
    (1 => B),
    (0 => A),
}

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

    #[test]
    fn test_combine_n() {
        assert_eq!(combine_n(&1, 0), 0);
        assert_eq!(combine_n(&2, 1), 2);
        assert_eq!(combine_n(&Some(2), 0), None);
        assert_eq!(combine_n(&Some(2), 4), Some(8));
    }

    #[test]
    fn test_combine_all() {
        assert_eq!(combine_all(&vec![1, 2, 3]), 6);

        let empty_vec_int: Vec<i32> = Vec::new();
        assert_eq!(combine_all(&empty_vec_int), 0);

        let empty_vec_opt_int: Vec<Option<i32>> = Vec::new();
        assert_eq!(combine_all(&empty_vec_opt_int), None);

        let vec_of_some_strings = vec![Some("Hello".to_owned()), Some(" World".to_owned())];
        assert_eq!(combine_all(&vec_of_some_strings),
                   Some("Hello World".to_owned()));

        let vec_of_no_hashes: Vec<HashSet<i32>> = Vec::new();
        assert_eq!(combine_all(&vec_of_no_hashes),
                   <HashSet<i32> as Monoid>::empty());

        let mut h1 = HashSet::new();
        h1.insert(1);
        let mut h2 = HashSet::new();
        h2.insert(2);
        let mut h3 = HashSet::new();
        h3.insert(3);
        let vec_of_hashes = vec![h1, h2, h3];
        let mut h_expected = HashSet::new();
        h_expected.insert(1);
        h_expected.insert(2);
        h_expected.insert(3);
        assert_eq!(combine_all(&vec_of_hashes), h_expected);
    }

    #[test]
    fn test_tuple() {
        let t1 = (1, 2.5f32, String::from("hi"), Some(3));
        let t2 = (1, 2.5f32, String::from(" world"), None);
        let t3 = (1, 2.5f32, String::from(", goodbye"), Some(10));
        let tuples = vec![t1, t2, t3];

        let expected = (3, 7.5f32, String::from("hi world, goodbye"), Some(13));
        assert_eq!(combine_all(&tuples), expected)
    }

}
<commit_msg>Add Monoid test for HashMap<commit_after>use super::semigroup::Semigroup;
use std::collections::*;
use std::hash::Hash;

pub trait Monoid {
    fn empty() -> Self;
}

/// Return this combined with itself `n` times.
pub fn combine_n<T>(o: &T, times: u32) -> T
    where T: Monoid + Semigroup + Clone
{
    if times == 0 {
        <T as Monoid>::empty()
    } else {
        super::semigroup::combine_n(o, times)
    }
}

/// Given a sequence of `xs`, combine them and return the total
///
/// ```
/// # use frust::monoid::*;
///
/// assert_eq!(combine_all(&vec![Some(1), Some(3)]), Some(4));
///
/// let empty_vec_opt_int:  Vec<Option<i32>> = Vec::new();
/// assert_eq!(combine_all(&empty_vec_opt_int), None);
///
/// let vec_of_some_strings = vec![Some("Hello".to_owned()), Some(" World".to_owned())];
/// assert_eq!(combine_all(&vec_of_some_strings), Some("Hello World".to_owned()));
/// ```
pub fn combine_all<T>(xs: &Vec<T>) -> T
    where T: Monoid + Semigroup + Clone
{
    let mut r = <T as Monoid>::empty();
    for i in xs {
        r = r.combine(i);
    }
    r
}

impl<T> Monoid for Option<T>
    where T: Semigroup + Clone
{
    fn empty() -> Self {
        None
    }
}

impl Monoid for String {
    fn empty() -> Self {
        String::new()
    }
}

impl<T> Monoid for Vec<T> {
    fn empty() -> Self {
        Vec::new()
    }
}

impl<T> Monoid for HashSet<T>
    where T: Hash + Eq
{
    fn empty() -> Self {
        HashSet::new()
    }
}

impl<K, V> Monoid for HashMap<K, V>
    where K: Eq + Hash + Clone,
          V: Semigroup + Clone
{
    fn empty() -> Self {
        HashMap::new()
    }
}

macro_rules! numeric_monoid_imps {
  ($($zero: expr; $tr:ty),*) => {
    $(
      impl Monoid for $tr {
        fn empty() -> Self { $zero }
      }
    )*
  }
}

numeric_monoid_imps! {
    0; i8,
    0; i16,
    0; i32,
    0; i64,
    0; u8,
    0; u16,
    0; u32,
    0; u64,
    0; isize,
    0; usize,
    0f32; f32,
    0f64; f64
}

macro_rules! tuple_impls {
    () => {}; // no more

    (($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
// Invoke recursive reversal of list that ends in the macro expansion implementation
// of the reversed list
//
        tuple_impls!([($idx, $typ);] $( ($nidx => $ntyp), )*);
        tuple_impls!($( ($nidx => $ntyp), )*); // invoke macro on tail
    };

// ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse
// + is empty (see next pattern)
//
    ([$(($accIdx: tt, $accTyp: ident);)+]  ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
      tuple_impls!([($idx, $typ); $(($accIdx, $accTyp); )*] $( ($nidx => $ntyp), ) *);
    };

// Finally expand into our implementation
    ([($idx:tt, $typ:ident); $( ($nidx:tt, $ntyp:ident); )*]) => {
        impl<$typ: Monoid, $( $ntyp: Monoid),*> Monoid for ($typ, $( $ntyp ),*) {
            fn empty() -> Self {
              (<$typ as Monoid>::empty(), $(<$ntyp as Monoid>::empty(), )*)
            }
        }
    }
}

tuple_impls! {
    (20 => U),
    (19 => T),
    (18 => S),
    (17 => R),
    (16 => Q),
    (15 => P),
    (14 => O),
    (13 => N),
    (12 => M),
    (11 => L),
    (10 => K),
    (9 => J),
    (8 => I),
    (7 => H),
    (6 => G),
    (5 => F),
    (4 => E),
    (3 => D),
    (2 => C),
    (1 => B),
    (0 => A),
}

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

    #[test]
    fn test_combine_n() {
        assert_eq!(combine_n(&1, 0), 0);
        assert_eq!(combine_n(&2, 1), 2);
        assert_eq!(combine_n(&Some(2), 0), None);
        assert_eq!(combine_n(&Some(2), 4), Some(8));
    }

    #[test]
    fn test_combine_all_basic() {
        assert_eq!(combine_all(&vec![1, 2, 3]), 6);

        let empty_vec_int: Vec<i32> = Vec::new();
        assert_eq!(combine_all(&empty_vec_int), 0);

        let empty_vec_opt_int: Vec<Option<i32>> = Vec::new();
        assert_eq!(combine_all(&empty_vec_opt_int), None);

        let vec_of_some_strings = vec![Some("Hello".to_owned()), Some(" World".to_owned())];
        assert_eq!(combine_all(&vec_of_some_strings),
                   Some("Hello World".to_owned()));
    }

    #[test]
    fn test_combine_all_hashset() {
        let vec_of_no_hashes: Vec<HashSet<i32>> = Vec::new();
        assert_eq!(combine_all(&vec_of_no_hashes),
                   <HashSet<i32> as Monoid>::empty());

        let mut h1 = HashSet::new();
        h1.insert(1);
        let mut h2 = HashSet::new();
        h2.insert(2);
        let mut h3 = HashSet::new();
        h3.insert(3);
        let vec_of_hashes = vec![h1, h2, h3];
        let mut h_expected = HashSet::new();
        h_expected.insert(1);
        h_expected.insert(2);
        h_expected.insert(3);
        assert_eq!(combine_all(&vec_of_hashes), h_expected);
    }

    #[test]
    fn test_combine_all_hashmap() {
        let vec_of_no_hashmaps: Vec<HashMap<i32, String>> = Vec::new();
        assert_eq!(combine_all(&vec_of_no_hashmaps),
                   <HashMap<i32, String> as Monoid>::empty());

        let mut h1: HashMap<i32, String> = HashMap::new();
        h1.insert(1, String::from("Hello"));  // h1 is HashMap( 1 -> "Hello")
        let mut h2: HashMap<i32, String> = HashMap::new();
        h2.insert(1, String::from(" World"));
        h2.insert(2, String::from("Goodbye"));  // h2 is HashMap( 1 -> " World", 2 -> "Goodbye")
        let mut h3: HashMap<i32, String> = HashMap::new();
        h3.insert(3, String::from("Cruel World")); // h3 is HashMap( 3 -> "Cruel World")
        let vec_of_hashes = vec![h1, h2, h3];

        let mut h_expected: HashMap<i32, String> = HashMap::new();
        h_expected.insert(1, String::from("Hello World"));
        h_expected.insert(2, String::from("Goodbye"));
        h_expected.insert(3, String::from("Cruel World")); // h_expected is HashMap ( 1 -> "Hello World", 2 -> "Goodbye", 3 -> "Cruel World")
        assert_eq!(combine_all(&vec_of_hashes), h_expected);
    }

    #[test]
    fn test_combine_all_tuple() {
        let t1 = (1, 2.5f32, String::from("hi"), Some(3));
        let t2 = (1, 2.5f32, String::from(" world"), None);
        let t3 = (1, 2.5f32, String::from(", goodbye"), Some(10));
        let tuples = vec![t1, t2, t3];

        let expected = (3, 7.5f32, String::from("hi world, goodbye"), Some(13));
        assert_eq!(combine_all(&tuples), expected)
    }

}
<|endoftext|>


<commit_before>pub mod dir;

use self::dir::FolderData;
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

#[derive(Copy, Clone)]
pub enum Prompt {
    EpisodeCompleted,
    UpdateScore,
    SeriesCompleted,
    AlreadyCompleted,
    BeginRewatch,
    ResumePausedSeries,
    PauseSeries(Status),
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    season: SeasonState,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn init(config: SeriesConfig<B>, mut dir: FolderData) -> Result<Series<B>, SeriesError> {
        dir.populate_season_data(&config)?;
        let ep_offset = dir.calculate_season_offset(0..config.season_num);
        let season = dir.seasons()[config.season_num].clone();

        let series = Series {
            config,
            dir,
            season,
            ep_offset,
        };

        Ok(series)
    }

    pub fn sync_remote_states(&mut self) -> Result<(), SeriesError> {
        self.season
            .sync_data_from_remote(&self.config, &self.dir, self.config.season_num)?;

        self.save_updated_season_data()
    }

    pub fn prepare_initial_state(&mut self) -> Result<(), SeriesError> {
        match self.season.state.status {
            Status::Watching | Status::Rewatching => {
                // Handle potential edge-case where all episodes have already been watched
                // but the series is still set in a watching state
                if !self.has_unwatched_episodes() {
                    self.prompt(Prompt::SeriesCompleted)?;
                }

                Ok(())
            }
            Status::PlanToWatch => self.set_list_entry_status(Status::Watching),
            Status::Completed => self.prompt(Prompt::AlreadyCompleted),
            Status::OnHold | Status::Dropped => self.prompt(Prompt::ResumePausedSeries),
        }
    }

    pub fn prompt(&mut self, prompt: Prompt) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match prompt {
            Prompt::EpisodeCompleted => {
                let total_episodes = state
                    .info
                    .episodes
                    .map(|e| Cow::Owned(e.to_string()))
                    .unwrap_or_else(|| Cow::Borrowed("?"));

                println!(
                    "[{}] episode {}/{} completed",
                    state.info.title, state.watched_episodes, total_episodes
                );

                self.update_list_entry()?;
                self.prompt_next_episode_options()
            }
            Prompt::UpdateScore => {
                let (min_score, max_score) = self.config.sync_service.formatted_score_range();

                println!(
                    "enter your score between {} and {} (press return to skip):",
                    min_score, max_score
                );

                // Read & parse score input until we get a valid one
                loop {
                    let input = match input::read_line() {
                        Ok(ref input) if input.is_empty() => return Ok(()),
                        Ok(input) => input,
                        Err(err) => {
                            eprintln!("failed to read score input: {}", err);
                            return Ok(());
                        }
                    };

                    match self.config.sync_service.parse_score(&input) {
                        Ok(score) => {
                            state.score = Some(score);
                            break;
                        }
                        Err(err) => eprintln!("error: {}", err),
                    }
                }

                self.update_list_entry()
            }
            Prompt::SeriesCompleted => {
                println!("[{}] completed!", state.info.title);
                self.set_list_entry_status(Status::Completed)?;
                self.prompt_series_completed_options()
            }
            Prompt::AlreadyCompleted => {
                println!("[{}] already completed", state.info.title);
                self.prompt_series_completed_options()
            }
            Prompt::BeginRewatch => {
                println!("[{}] starting rewatch", state.info.title);
                println!("do you want to reset the start and end dates? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.start_date = None;
                    state.finish_date = None;
                }

                self.set_list_entry_status(Status::Rewatching)
            }
            Prompt::ResumePausedSeries => {
                println!(
                    "[{}] was previously put on hold or dropped",
                    state.info.title
                );

                println!("do you want to watch it from the beginning? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.watched_episodes = 0;
                }

                self.set_list_entry_status(Status::Watching)
            }
            Prompt::PauseSeries(status) => {
                self.set_list_entry_status(status)?;

                println!("do you want to remove the episodes on disk? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    self.dir.try_remove_dir();
                }

                Ok(())
            }
        }
    }

    pub fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_completed_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!(
            "\t[r] rate{}\n\t[w] rewatch\n\t[d] delete local files\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_completed_options()
            }
            "w" => {
                self.prompt(Prompt::BeginRewatch)?;
                self.play_all_episodes()
            }
            "d" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("[{}] series options:", self.season.state.info.title);
        println!(
            "\t[r] rate{}\n\t[dd] delete local files\n\t[d] drop\n\t[h] put on hold\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_options()
            }
            "dd" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => self.prompt_series_options(),
        }
    }

    fn format_entry_score(&self) -> Option<String> {
        let state = &self.season.state;

        match state.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    fn set_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match status {
            Status::Watching => {
                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if state.status != Status::OnHold {
                    state.start_date = Some(Local::today().naive_local());
                }

                state.finish_date = None;
            }
            Status::Rewatching => {
                if state.start_date.is_none() {
                    state.start_date = Some(Local::today().naive_local());
                    state.finish_date = None;
                }

                state.watched_episodes = 0;
            }
            Status::Completed => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::Dropped => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        state.status = status;
        self.update_list_entry()
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.season.needs_sync = self.config.offline_mode;
        self.save_updated_season_data()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.season.state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.get_episode(absolute_ep)?.clone();

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        let state = &mut self.season.state;
        state.watched_episodes = episode.max(state.watched_episodes);

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        loop {
            let next_episode = self.season.state.watched_episodes + 1;
            self.play_episode(next_episode)?;

            if self.has_unwatched_episodes() {
                self.prompt(Prompt::EpisodeCompleted)?;
            } else {
                self.prompt(Prompt::SeriesCompleted)?;
                break;
            }
        }

        Ok(())
    }

    pub fn has_unwatched_episodes(&self) -> bool {
        let state = &self.season.state;

        match state.info.episodes {
            Some(total_eps) if total_eps > state.watched_episodes => true,
            _ => false,
        }
    }

    fn save_updated_season_data(&mut self) -> Result<(), SeriesError> {
        if self.config.season_num >= self.dir.seasons().len() {
            return Ok(());
        }

        self.dir.seasons_mut()[self.config.season_num] = self.season.clone();
        self.dir.save()
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    println!(
        "select season {} by entering the number next to its name:\n",
        1 + season
    );

    println!("0 [manual search]");

    for (i, series) in found.iter().enumerate() {
        println!("{} [{}]", 1 + i, series.title);
    }

    let index = input::read_range(0, found.len())?;

    match index {
        0 => {
            println!("enter the name you want to search for:");
            let name = input::read_line()?;

            search_for_series_info(backend, &name, season)
        }
        _ => Ok(found.swap_remove(index - 1)),
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SeasonState {
    #[serde(flatten)]
    pub state: AnimeEntry,
    pub needs_info: bool,
    pub needs_sync: bool,
}

impl SeasonState {
    pub fn sync_info_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if !self.needs_info {
            return Ok(());
        }

        if config.offline_mode {
            // The series title is the only thing that can really be used when offline.
            // While things like the number of episode files present could be used as the
            // series episode count, it is common to only partially have a series downloaded
            self.state.info.title = dir.series_info.name.clone();
            return Ok(());
        }

        let info = search_for_series_info(&config.sync_service, &dir.series_info.name, season_num)?;

        self.state.info = info;
        // We only want to set this flag when online, since offline mode only provides
        // very basic information at best
        self.needs_info = false;
        Ok(())
    }

    pub fn sync_data_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        self.sync_info_from_remote(config, dir, season_num)?;

        // There's no information we can sync when offline
        if config.offline_mode {
            return Ok(());
        }

        let entry = config
            .sync_service
            .get_list_entry(self.state.info.clone())?;

        if let Some(entry) = entry {
            // If we don't have new data to report, we should sync the data from the backend to keep up with
            // any changes made outside of the program
            if !self.needs_sync {
                self.state = entry;
            }
        }

        Ok(())
    }
}
<commit_msg>[series] Use the number of episodes on disk for the series episode count when offline<commit_after>pub mod dir;

use self::dir::FolderData;
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

#[derive(Copy, Clone)]
pub enum Prompt {
    EpisodeCompleted,
    UpdateScore,
    SeriesCompleted,
    AlreadyCompleted,
    BeginRewatch,
    ResumePausedSeries,
    PauseSeries(Status),
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    season: SeasonState,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn init(config: SeriesConfig<B>, mut dir: FolderData) -> Result<Series<B>, SeriesError> {
        dir.populate_season_data(&config)?;
        let ep_offset = dir.calculate_season_offset(0..config.season_num);
        let season = dir.seasons()[config.season_num].clone();

        let series = Series {
            config,
            dir,
            season,
            ep_offset,
        };

        Ok(series)
    }

    pub fn sync_remote_states(&mut self) -> Result<(), SeriesError> {
        self.season
            .sync_data_from_remote(&self.config, &self.dir, self.config.season_num)?;

        self.save_updated_season_data()
    }

    pub fn prepare_initial_state(&mut self) -> Result<(), SeriesError> {
        match self.season.state.status {
            Status::Watching | Status::Rewatching => {
                // Handle potential edge-case where all episodes have already been watched
                // but the series is still set in a watching state
                if !self.has_unwatched_episodes() {
                    self.prompt(Prompt::SeriesCompleted)?;
                }

                Ok(())
            }
            Status::PlanToWatch => self.set_list_entry_status(Status::Watching),
            Status::Completed => self.prompt(Prompt::AlreadyCompleted),
            Status::OnHold | Status::Dropped => self.prompt(Prompt::ResumePausedSeries),
        }
    }

    pub fn prompt(&mut self, prompt: Prompt) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match prompt {
            Prompt::EpisodeCompleted => {
                let total_episodes = state
                    .info
                    .episodes
                    .map(|e| Cow::Owned(e.to_string()))
                    .unwrap_or_else(|| Cow::Borrowed("?"));

                println!(
                    "[{}] episode {}/{} completed",
                    state.info.title, state.watched_episodes, total_episodes
                );

                self.update_list_entry()?;
                self.prompt_next_episode_options()
            }
            Prompt::UpdateScore => {
                let (min_score, max_score) = self.config.sync_service.formatted_score_range();

                println!(
                    "enter your score between {} and {} (press return to skip):",
                    min_score, max_score
                );

                // Read & parse score input until we get a valid one
                loop {
                    let input = match input::read_line() {
                        Ok(ref input) if input.is_empty() => return Ok(()),
                        Ok(input) => input,
                        Err(err) => {
                            eprintln!("failed to read score input: {}", err);
                            return Ok(());
                        }
                    };

                    match self.config.sync_service.parse_score(&input) {
                        Ok(score) => {
                            state.score = Some(score);
                            break;
                        }
                        Err(err) => eprintln!("error: {}", err),
                    }
                }

                self.update_list_entry()
            }
            Prompt::SeriesCompleted => {
                println!("[{}] completed!", state.info.title);
                self.set_list_entry_status(Status::Completed)?;
                self.prompt_series_completed_options()
            }
            Prompt::AlreadyCompleted => {
                println!("[{}] already completed", state.info.title);
                self.prompt_series_completed_options()
            }
            Prompt::BeginRewatch => {
                println!("[{}] starting rewatch", state.info.title);
                println!("do you want to reset the start and end dates? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.start_date = None;
                    state.finish_date = None;
                }

                self.set_list_entry_status(Status::Rewatching)
            }
            Prompt::ResumePausedSeries => {
                println!(
                    "[{}] was previously put on hold or dropped",
                    state.info.title
                );

                println!("do you want to watch it from the beginning? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    state.watched_episodes = 0;
                }

                self.set_list_entry_status(Status::Watching)
            }
            Prompt::PauseSeries(status) => {
                self.set_list_entry_status(status)?;

                println!("do you want to remove the episodes on disk? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    self.dir.try_remove_dir();
                }

                Ok(())
            }
        }
    }

    pub fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_completed_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!(
            "\t[r] rate{}\n\t[w] rewatch\n\t[d] delete local files\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_completed_options()
            }
            "w" => {
                self.prompt(Prompt::BeginRewatch)?;
                self.play_all_episodes()
            }
            "d" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    pub fn prompt_series_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("[{}] series options:", self.season.state.info.title);
        println!(
            "\t[r] rate{}\n\t[dd] delete local files\n\t[d] drop\n\t[h] put on hold\n\t[x] exit",
            current_score_text
        );

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "r" => {
                self.prompt(Prompt::UpdateScore)?;
                self.prompt_series_options()
            }
            "dd" => {
                self.dir.try_remove_dir();
                Err(SeriesError::RequestExit)
            }
            "d" | "h" => {
                let status = if input == "d" {
                    Status::Dropped
                } else {
                    Status::OnHold
                };

                self.prompt(Prompt::PauseSeries(status))?;
                Err(SeriesError::RequestExit)
            }
            "x" => Err(SeriesError::RequestExit),
            _ => self.prompt_series_options(),
        }
    }

    fn format_entry_score(&self) -> Option<String> {
        let state = &self.season.state;

        match state.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    fn set_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        let state = &mut self.season.state;

        match status {
            Status::Watching => {
                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if state.status != Status::OnHold {
                    state.start_date = Some(Local::today().naive_local());
                }

                state.finish_date = None;
            }
            Status::Rewatching => {
                if state.start_date.is_none() {
                    state.start_date = Some(Local::today().naive_local());
                    state.finish_date = None;
                }

                state.watched_episodes = 0;
            }
            Status::Completed => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::Dropped => {
                if state.finish_date.is_none() {
                    state.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        state.status = status;
        self.update_list_entry()
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.season.needs_sync = self.config.offline_mode;
        self.save_updated_season_data()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.season.state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.get_episode(absolute_ep)?.clone();

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        let state = &mut self.season.state;
        state.watched_episodes = episode.max(state.watched_episodes);

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        loop {
            let next_episode = self.season.state.watched_episodes + 1;
            self.play_episode(next_episode)?;

            if self.has_unwatched_episodes() {
                self.prompt(Prompt::EpisodeCompleted)?;
            } else {
                self.prompt(Prompt::SeriesCompleted)?;
                break;
            }
        }

        Ok(())
    }

    pub fn has_unwatched_episodes(&self) -> bool {
        let state = &self.season.state;

        match state.info.episodes {
            Some(total_eps) if total_eps > state.watched_episodes => true,
            _ => false,
        }
    }

    fn save_updated_season_data(&mut self) -> Result<(), SeriesError> {
        if self.config.season_num >= self.dir.seasons().len() {
            return Ok(());
        }

        self.dir.seasons_mut()[self.config.season_num] = self.season.clone();
        self.dir.save()
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    println!(
        "select season {} by entering the number next to its name:\n",
        1 + season
    );

    println!("0 [manual search]");

    for (i, series) in found.iter().enumerate() {
        println!("{} [{}]", 1 + i, series.title);
    }

    let index = input::read_range(0, found.len())?;

    match index {
        0 => {
            println!("enter the name you want to search for:");
            let name = input::read_line()?;

            search_for_series_info(backend, &name, season)
        }
        _ => Ok(found.swap_remove(index - 1)),
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct SeasonState {
    #[serde(flatten)]
    pub state: AnimeEntry,
    pub needs_info: bool,
    pub needs_sync: bool,
}

impl SeasonState {
    pub fn sync_info_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if !self.needs_info {
            return Ok(());
        }

        // When offline, use data from the files on disk
        if config.offline_mode {
            // We want to use the highest episode number we have on disk to represent the
            // total number of episodes for a series. While it's possible that only part of the
            // series is downloaded, it is much more likely that the entire series is there if
            // the user is using offline mode. This will also allow the user to never have to use
            // online mode to get the real series information first.
            let num_eps = dir.series_info.episodes.keys().max();

            self.state.info.title = dir.series_info.name.clone();
            self.state.info.episodes = num_eps.cloned();

            return Ok(());
        }

        let info = search_for_series_info(&config.sync_service, &dir.series_info.name, season_num)?;

        self.state.info = info;
        // We only want to set this flag when online, since offline mode only provides
        // very basic information at best
        self.needs_info = false;
        Ok(())
    }

    pub fn sync_data_from_remote<B>(
        &mut self,
        config: &SeriesConfig<B>,
        dir: &FolderData,
        season_num: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        self.sync_info_from_remote(config, dir, season_num)?;

        // There's no information we can sync when offline
        if config.offline_mode {
            return Ok(());
        }

        let entry = config
            .sync_service
            .get_list_entry(self.state.info.clone())?;

        if let Some(entry) = entry {
            // If we don't have new data to report, we should sync the data from the backend to keep up with
            // any changes made outside of the program
            if !self.needs_sync {
                self.state = entry;
            }
        }

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