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

use traits::HasValues;
use point::Point;

/// Find the points in a line between the current point and the one provided
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(3, 4, 10);
/// let set: HashSet<Point> = line(&point, &other);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(2, 3, 8)));
/// assert!(set.contains(&Point::new(3, 3, 9)));
/// assert!(set.contains(&Point::new(3, 4, 10)));
/// assert_eq!(set.len(), 5);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(3, 4);
/// let set: HashSet<Point> = line(&point, &other);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(2, 3)));
/// assert!(set.contains(&Point::new_2d(3, 3)));
/// assert!(set.contains(&Point::new_2d(3, 4)));
/// assert_eq!(set.len(), 5);
/// ```
pub fn line<T: HasValues>(point: &T, other: &T) -> HashSet<Point> {
  util::line(point, other, None, None)
}

/// Find the points within range in a line through two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line_through, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(2, 2, 6);
/// let set: HashSet<Point> = line_through(&point, &other, 3);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(3, 2, 7)));
/// assert!(set.contains(&Point::new(4, 2, 8)));
/// assert_eq!(set.len(), 4);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line_through, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(2, 2);
/// let set: HashSet<Point> = line_through(&point, &other, 3);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(3, 2)));
/// assert!(set.contains(&Point::new_2d(4, 2)));
/// assert_eq!(set.len(), 4);
/// ```
pub fn line_through<T: HasValues>(
  point: &T,
  other: &T,
  range: i32,
) -> HashSet<Point> {
  util::line(point, other, Some(range), None)
}

/// Find unblocked points in a line between two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(3, 4, 10);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new(3, 3, 9));
///
/// let set: HashSet<Point> = ray(&point, &other, &opaque);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(2, 3, 8)));
/// assert!(set.contains(&Point::new(3, 3, 9)));
/// assert_eq!(set.len(), 4);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(3, 4);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new_2d(3, 3));
///
/// let set: HashSet<Point> = ray(&point, &other, &opaque);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(2, 3)));
/// assert!(set.contains(&Point::new_2d(3, 3)));
/// assert_eq!(set.len(), 4);
/// ```
pub fn ray<T: HasValues>(
  point: &T,
  other: &T,
  opaque: &HashSet<Point>,
) -> HashSet<Point> {
  util::line(point, other, None, Some(opaque))
}

/// Find unblocked points within range in a line through two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray_through, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(2, 2, 6);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new(3, 2, 7));
///
/// let set: HashSet<Point> = ray_through(&point, &other, 3, &opaque);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(3, 2, 7)));
/// assert_eq!(set.len(), 3);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray_through, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(2, 2);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new_2d(3, 2));
///
/// let set: HashSet<Point> = ray_through(&point, &other, 3, &opaque);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(3, 2)));
/// assert_eq!(set.len(), 3);
/// ```
pub fn ray_through<T: HasValues>(
  point: &T,
  other: &T,
  range: i32,
  opaque: &HashSet<Point>,
) -> HashSet<Point> {
  util::line(point, other, Some(range), Some(opaque))
}

mod util {
  use std::collections::HashSet;

  use traits::HasValues;
  use point::Point;
  use distance::{distance, distance_2d};

  /// Linear interpolation of floats with specified offset
  pub fn lerp(a: i32, b: i32, t: f32, o: f32) -> f32 {
    let a = a as f32 + o;
    let b = b as f32 + o;

    a + ((b - a) * t)
  }

  /// Linear interpolation of points with small offset
  ///
  /// The offset is used to prevent the interpolation from falling exactly
  /// on a border between two points. It is eliminated with rounding later.
  pub fn point_lerp<T: HasValues>(
    a: &T,
    b: &T,
    t: f32
  ) -> (f32, f32, f32, f32) {
    let (qa, ra, sa, ta) = a.values_cube();
    let (qb, rb, sb, tb) = b.values_cube();

    let result = (
      lerp(qa, qb, t, 1e-6),
      lerp(ra, rb, t, 1e-6),
      lerp(sa, sb, t, -2e-6),
      lerp(ta, tb, t, 1e-6),
    );

    result
  }

  /// Round a float point back to a standard point
  pub fn point_round((q, r, s, t): (f32, f32, f32, f32)) -> Point {
    let mut rq = q.round();
    let mut rr = r.round();

    let rs = s.round();
    let rt = t.round();

    let dq = (rq - q).abs();
    let dr = (rr - r).abs();
    let ds = (rs - s).abs();

    if (dq > ds) && (dq > dr) {
      rq = -rs - rr;
    } else if ds < dr {
      rr = -rq - rs;
    }

    let point: Point = Point::new(rq as i32, rr as i32, rt as i32);

    point
  }

  /// Find the points in a line between two points
  ///
  /// Optionally provide a range. The line will end at that range.
  ///
  /// Optionally provide a set of opaque point which are impassable.
  pub fn line<T: HasValues>(
    point: &T,
    other: &T,
    range: Option<i32>,
    opaque: Option<&HashSet<Point>>,
  ) -> HashSet<Point> {
    let mut set: HashSet<Point> = HashSet::new();

    if point.values() == other.values() {
      set.insert(point.to_point());

      return set;
    }

    let distance: i32 = if point.values_2d() == other.values_2d() {
      distance(point, other)
    } else {
      distance_2d(point, other)
    };

    let empty: HashSet<Point> = HashSet::new();
    let opaque: &HashSet<Point> = opaque.unwrap_or(&empty);
    let should_check_opaque: bool = !opaque.is_empty();

    for index in 0..range.unwrap_or(distance) + 1 {
      let t: f32 = index as f32 / distance as f32;
      let lerp: (f32, f32, f32, f32) = point_lerp(point, other, t);
      let found: Point = point_round(lerp);
      let should_break = should_check_opaque && opaque.contains(&found);

      set.insert(found);

      if should_break {
        break;
      }
    }

    set
  }

}

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

  use point::Point;

  use super::util;

  #[test]
  fn lerp() {
    let result = util::lerp(1, 2, 0.5, 1e-6);

    assert_eq!(result, 1.5 + 1e-6);
  }

  #[test]
  fn point_lerp() {
    let point: Point = Point::new(1, 2, 5);
    let other: Point = Point::new(3, 4, 10);
    let result = util::point_lerp(&point, &other, 0.5);
    let expect = (2f32 + 1e-6, 3f32 + 1e-6, -5f32 - 2e-6, 7.5f32 + 1e-6);

    assert_eq!(result, expect);
  }

  #[test]
  fn point_round() {
    let coordinates = (1.6, 1.6, -3.2, 2.5);
    let point: Point = util::point_round(coordinates);

    assert_eq!(point, Point::new(2, 1, 3));
  }

  #[test]
  fn line_vertical() {
    let point: Point = Point::new(1, 2, 5);
    let other: Point = Point::new(1, 2, 7);
    let line: HashSet<Point> = util::line(&point, &other, None, None);

    assert!(line.contains(&Point::new(1, 2, 5)));
    assert!(line.contains(&Point::new(1, 2, 6)));
    assert!(line.contains(&Point::new(1, 2, 7)));
  }

}

<commit_msg>Closes #39: Track bearing<commit_after>use std::collections::HashSet;

use traits::HasValues;
use point::Point;

/// Find the points in a line between the current point and the one provided
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(3, 4, 10);
/// let set: HashSet<Point> = line(&point, &other);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(2, 3, 8)));
/// assert!(set.contains(&Point::new(3, 3, 9)));
/// assert!(set.contains(&Point::new(3, 4, 10)));
/// assert_eq!(set.len(), 5);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(3, 4);
/// let set: HashSet<Point> = line(&point, &other);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(2, 3)));
/// assert!(set.contains(&Point::new_2d(3, 3)));
/// assert!(set.contains(&Point::new_2d(3, 4)));
/// assert_eq!(set.len(), 5);
/// ```
pub fn line<T: HasValues>(point: &T, other: &T) -> HashSet<Point> {
  util::line(point, other, None, None)
}

/// Find the points within range in a line through two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line_through, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(2, 2, 6);
/// let set: HashSet<Point> = line_through(&point, &other, 3);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(3, 2, 7)));
/// assert!(set.contains(&Point::new(4, 2, 8)));
/// assert_eq!(set.len(), 4);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{line_through, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(2, 2);
/// let set: HashSet<Point> = line_through(&point, &other, 3);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(3, 2)));
/// assert!(set.contains(&Point::new_2d(4, 2)));
/// assert_eq!(set.len(), 4);
/// ```
pub fn line_through<T: HasValues>(
  point: &T,
  other: &T,
  range: i32,
) -> HashSet<Point> {
  util::line(point, other, Some(range), None)
}

/// Find unblocked points in a line between two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(3, 4, 10);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new(3, 3, 9));
///
/// let set: HashSet<Point> = ray(&point, &other, &opaque);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(2, 3, 8)));
/// assert!(set.contains(&Point::new(3, 3, 9)));
/// assert_eq!(set.len(), 4);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(3, 4);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new_2d(3, 3));
///
/// let set: HashSet<Point> = ray(&point, &other, &opaque);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(2, 3)));
/// assert!(set.contains(&Point::new_2d(3, 3)));
/// assert_eq!(set.len(), 4);
/// ```
pub fn ray<T: HasValues>(
  point: &T,
  other: &T,
  opaque: &HashSet<Point>,
) -> HashSet<Point> {
  util::line(point, other, None, Some(opaque))
}

/// Find unblocked points within range in a line through two points
///
/// # Example
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray_through, Point};
///
/// let point: Point = Point::new(1, 2, 5);
/// let other: Point = Point::new(2, 2, 6);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new(3, 2, 7));
///
/// let set: HashSet<Point> = ray_through(&point, &other, 3, &opaque);
///
/// assert!(set.contains(&Point::new(1, 2, 5)));
/// assert!(set.contains(&Point::new(2, 2, 6)));
/// assert!(set.contains(&Point::new(3, 2, 7)));
/// assert_eq!(set.len(), 3);
/// ```
///
/// ```
/// use std::collections::HashSet;
///
/// use hex_math::{ray_through, Point};
///
/// let point: Point = Point::new_2d(1, 2);
/// let other: Point = Point::new_2d(2, 2);
/// let mut opaque: HashSet<Point> = HashSet::new();
///
/// opaque.insert(Point::new_2d(3, 2));
///
/// let set: HashSet<Point> = ray_through(&point, &other, 3, &opaque);
///
/// assert!(set.contains(&Point::new_2d(1, 2)));
/// assert!(set.contains(&Point::new_2d(2, 2)));
/// assert!(set.contains(&Point::new_2d(3, 2)));
/// assert_eq!(set.len(), 3);
/// ```
pub fn ray_through<T: HasValues>(
  point: &T,
  other: &T,
  range: i32,
  opaque: &HashSet<Point>,
) -> HashSet<Point> {
  util::line(point, other, Some(range), Some(opaque))
}

mod util {
  use std::collections::HashSet;

  use traits::HasValues;
  use point::Point;
  use distance::{distance, distance_2d};

  /// Return the floats one step along a line between two points
  pub fn step_size<T: HasValues>(
    a: &T,
    b: &T,
    step: f32
  ) -> (f32, f32, f32, f32) {

    let lerp = |x: i32, y: i32, offset: f32| offset + (y - x) as f32 * step;
    let (qa, ra, sa, ta) = a.values_cube();
    let (qb, rb, sb, tb) = b.values_cube();

    let result = (
      lerp(qa, qb, 1e-6),
      lerp(ra, rb, 1e-6),
      lerp(sa, sb, -2e-6),
      lerp(ta, tb, 1e-6),
    );

    result

  }

  /// Return the floats a number of sized steps away from a point
  pub fn take_steps<T: HasValues>(
    steps: i32,
    (dq, dr, ds, dt): (f32, f32, f32, f32),
    point: &T
  ) -> (f32, f32, f32, f32) {

    let (q, r, s, t) = point.values_cube();
    let step = |x: f32, y: i32| (x * steps as f32) + y as f32;

    let result = (
      step(dq, q),
      step(dr, r),
      step(ds, s),
      step(dt, t),
    );

    result

  }

  /// Round a float point back to a standard point
  pub fn point_round((q, r, s, t): (f32, f32, f32, f32)) -> Point {
    let mut rq = q.round();
    let mut rr = r.round();

    let rs = s.round();
    let rt = t.round();

    let dq = (rq - q).abs();
    let dr = (rr - r).abs();
    let ds = (rs - s).abs();

    if (dq > ds) && (dq > dr) {
      rq = -rs - rr;
    } else if ds < dr {
      rr = -rq - rs;
    }

    let point: Point = Point::new(rq as i32, rr as i32, rt as i32);

    point
  }

  /// Find the points in a line between two points
  ///
  /// Optionally provide a range. The line will end at that range.
  ///
  /// Optionally provide a set of opaque point which are impassable.
  pub fn line<T: HasValues>(
    point: &T,
    other: &T,
    range: Option<i32>,
    opaque: Option<&HashSet<Point>>,
  ) -> HashSet<Point> {
    let mut set: HashSet<Point> = HashSet::new();

    if point.values() == other.values() {
      set.insert(point.to_point());

      return set;
    }

    let distance: i32 = if point.values_2d() == other.values_2d() {
      distance(point, other)
    } else {
      distance_2d(point, other)
    };

    let empty: HashSet<Point> = HashSet::new();
    let opaque: &HashSet<Point> = opaque.unwrap_or(&empty);
    let should_check_opaque: bool = !opaque.is_empty();

    let step: f32 = 1f32 / distance as f32;
    let size: (f32, f32, f32, f32) = step_size(point, other, step);

    for index in 0..range.unwrap_or(distance) + 1 {
      let found: Point = point_round(take_steps(index, size, point));
      let should_break: bool = should_check_opaque && opaque.contains(&found);

      set.insert(found);

      if should_break {
        break;
      }
    }

    set
  }

}

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

  use point::Point;

  use super::util;

  #[test]
  fn step_size() {
    let point: Point = Point::new(1, 2, 5);
    let other: Point = Point::new(1, 12, 5);
    let size: (f32, f32, f32, f32) = util::step_size(&point, &other, 1f32);

    assert_eq!((1e-6, 10f32 + 1e-6, -10f32 - 2e-6, 1e-6), size);
  }

  #[test]
  fn take_steps() {
    let steps = 2;
    let size = (2f32, 2f32, -4f32, 2f32);
    let point: Point = Point::new(1, 2, 5);
    let step: (f32, f32, f32, f32) = util::take_steps(steps, size, &point);

    assert_eq!((5f32, 6f32, -11f32, 9f32), step);
  }

  #[test]
  fn point_round() {
    let coordinates = (1.6, 1.6, -3.2, 2.5);
    let point: Point = util::point_round(coordinates);

    assert_eq!(point, Point::new(2, 1, 3));
  }

  #[test]
  fn line_vertical() {
    let point: Point = Point::new(1, 2, 5);
    let other: Point = Point::new(1, 2, 7);
    let line: HashSet<Point> = util::line(&point, &other, None, None);

    assert!(line.contains(&Point::new(1, 2, 5)));
    assert!(line.contains(&Point::new(1, 2, 6)));
    assert!(line.contains(&Point::new(1, 2, 7)));
  }

}

<|endoftext|>


<commit_before>use name::*;
use art::*;
use std::hash;
use std::hash::Hash;

/// Nominal, artful lists: Lists with names and articulation points
#[derive(Show,Hash,PartialEq,Eq)]
pub enum List<'x,T> {
    Nil,
    Cons(T, Box<List<'x,T>>),
    Name(Name, Box<List<'x,T>>),
    Art(Art<'x, Box<List<'x,T>>>)
}

pub fn copy<'x,T:'x>(list:List<'x,T>) -> List<'x,T> {
    match list {
        List::Nil         => List::Nil,
        List::Cons(hd,tl) => List::Cons(hd, box copy(*tl)),
        List::Art(art)    => copy(*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box copy(*tl)) ;
            List::Name(nm2, box List::Art(art))
        }
    }
}

pub fn map<'x, T:'x, S:'x>
(f:UAr<'x,T,S>, list:List<'x,T>) -> List<'x,S>
{
    match list {
        List::Nil         => List::Nil,
        List::Cons(hd,tl) => List::Cons((*f).call(hd), box map(f,*tl)),
        // - - - - - boilerplate cases - - - - - -
        List::Art(art)    => map(f,*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box map(f,*tl));
            List::Name(nm2, box List::Art(art))
        }
    }
}

pub fn contract<'x,F,G,T:'x>
(f:&'x F, g:&'x G, list:List<'x,T>) -> List<'x,T>
where F:Fn(&T,&T) -> bool + 'x,
      G:Fn(T,T) -> T + 'x
{
    match list {
        List::Nil => List::Nil,
        List::Cons(hd1, box list) => {
            match list {
                List::Nil => List::Cons(hd1, box List::Nil),
                List::Cons(hd2, tl) => {
                    if (*f)(&hd1,&hd2) {
                        List::Cons((*g)(hd1,hd2), box contract(f,g,*tl))
                    } else {
                        List::Cons(hd1, box contract(f, g, List::Cons(hd2, tl)))
                    } },
                // - - - - - boilerplate cases - - - - - -
                List::Art(art) => contract(f,g,List::Cons(hd1,force(art))),
                List::Name(nm, tl) => {
                    let (nm1,nm2) = fork(nm) ;
                    let art = nart!(nm1, box contract(f,g,List::Cons(hd1,tl))) ;
                    List::Name(nm2, box List::Art(art))
                }
            }
        },
        // - - - - - boilerplate cases - - - - - -
        List::Art(art)    => contract(f,g,*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box contract(f,g,*tl)) ;
            List::Name(nm2, box List::Art(art))
        },
    }
}

pub fn reduce<'x,F:'x,G:'x,T:'x>
(f:&'x F, g:&'x G, list:List<'x,T>) -> Option<T>
where F: Fn(&T,&T) -> bool,
      G: Fn(T, T) -> T,
{
    match list {
        List::Nil => None,
        List::Cons(hd, box list) => {
            match list {
                List::Nil => Some(hd),
                List::Cons(hd2, tl) => {
                    let hd3 = (*g)(hd,hd2) ;
                    let list = contract(f, g, List::Cons(hd3, tl)) ;
                    reduce (f, g, list)
                },
                // - - - - - boilerplate cases - - - - - -
                List::Name(_,tl) => reduce (f, g, *tl),
                List::Art(art)   => reduce (f, g, *force(art)),
            }},
        // - - - - - boilerplate cases - - - - - -
        List::Name(_,tl) => reduce(f, g, *tl),
        List::Art(art)   => reduce(f, g, *force(art)),
    }
}

pub fn merge<'x,T:'x,Ord:'x>
(ord:&'x Ord, list1:List<'x,T>, list2:List<'x,T>) -> List<'x,T>
where Ord: Fn(&T,&T) -> bool
{
    match (list1, list2) {
        (List::Nil, list2) => list2,
        (list1, List::Nil) => list1,
        (List::Cons(hd1,tl1),
         List::Cons(hd2,tl2)) =>
            if (*ord)(&hd1,&hd2) {
                List::Cons(hd1, box merge(ord, *tl1, List::Cons(hd2,tl2)))
            } else {
                List::Cons(hd2, box merge(ord, List::Cons(hd1,tl1), *tl2))
            },
        // - - - - - boilerplate cases - - - - -
        (List::Name(nm,tl), list2) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box merge(ord, *tl, list2)) ;
            List::Name(nm2, box List::Art(art))
        },
        (list1, List::Name(nm,tl)) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box merge(ord, list1, *tl)) ;
            List::Name(nm2, box List::Art(art))
        },
        (List::Art(art), list2) => merge(ord, *force(art), list2),
        (list1, List::Art(art)) => merge(ord, list1, *force(art)),
    }
}

pub fn singletons<'x,T:'x>
(list:List<'x,T>) -> List<'x,List<'x,T>>
{
    match list {
        List::Nil => List::Nil,
        List::Cons(hd, tl) => {
            List::Cons( List::Cons(hd, box List::Nil),
                        box singletons(*tl) )
        },
        List::Art(art) => singletons(*force(art)),
        List::Name(nm, tl) => {
            let (nm1, nm2) = fork(nm) ;
            let art = nart!(nm2, box singletons(*tl)) ;
            List::Name(nm1, box List::Art(art))
        }
    }
}

pub fn mergesort<'x,T:'x,Ord:'x>
(ord:&'x Ord, list:List<'x,T>) -> List<'x,T>
where Ord: Fn(&T,&T) -> bool, T:Hash
{
    let c = move |&: list1:&List<'x,T>,list2:&List<'x,T>|
    hash::hash(list1) < hash::hash(list2);

    let m = move |&: list1:List<'x,T>,list2:List<'x,T>|
    merge(ord,list1,list2);

    match reduce(&c, &m, singletons(list)) {
        None => List::Nil,
        Some(list) => list
    }
}

#[test]
pub fn construct_list () {
    let z : List<int> = List::Nil;
    let y : List<int> = List::Cons(1, box z);
    let x : List<int> = List::Art(cell(symbol(format!("two")), box y));
    let l : List<int> = List::Name(symbol(format!("one")), box x);
    println!("constructed list: {}", l);
}

// TODO: Rustup: Fix this:

 /// An iterator over the items in a list.
pub struct ListItems<'x, T: 'x> {
    list: &'x List<'x,T>
}

impl<'x,T> List<'x,T> {
    type Item = &'x T ;
    /// Get an iterator over the items in a list.
    pub fn iter(&'x self) -> ListItems<'x, T> {
        ListItems {
            list: self
        }
    }

}

impl<'x, T:'x> Iterator for ListItems<'x, T> {
    type Item = &'x T ;
    fn next(&mut self) -> Option<&'x T> {
        match *self.list {
            List::Cons(ref hd, ref tl) => {
                self.list = &**tl;
                Some(hd)
            },
            List::Name(_, ref list) => {
                self.list = &**list;
                self.next()
            },
            List::Art(ref art) => {
                self.list = &**force_ref(art);
                self.next()
            },
            List::Nil => None
        }
    }
}

// pub enum NameOrContent<T> { Name(Name),Content(T) }

// pub fn clone_iter<'x, 'y, T:'y>
// (iter:&'x mut Iterator) -> List<'y,T>
// where T : Clone
// {
//     match iter.next() {
//         None => List::Nil,
//         Some(x) => match *x {
//             NameOrContent::Content(ref hd) => List::Cons(hd.clone(), box clone_iter(iter)),
//             NameOrContent::Name(ref nm) => {
//                 let (nm1,nm2) = fork(nm.clone());
//                 let rest = clone_iter(iter);
//                 List::Name(nm1, box List::Art( cell(nm2, box rest )))
//             }
//         }
//     }
// }

// pub fn from_iter<'x, 'y, T:'y>
// (iter:&'x mut Iterator) -> List<'y,T>
// where T : Clone
// {
//     match iter.next() {
//         None => List::Nil,
//         Some(x) => match x {
//             NameOrContent::Content(hd) => List::Cons(hd, box from_iter(iter)),
//             NameOrContent::Name(nm) => {
//                 let (nm1,nm2) = fork(nm.clone());
//                 let rest = from_iter(iter);
//                 List::Name(nm1, box List::Art( cell(nm2, box rest )))
//             }
//         }
//     }
// }
<commit_msg>dont understand how to consume Iterators, generically<commit_after>use name::*;
use art::*;
use std::hash;
use std::hash::Hash;

/// Nominal, artful lists: Lists with names and articulation points
#[derive(Show,Hash,PartialEq,Eq)]
pub enum List<'x,T> {
    Nil,
    Cons(T, Box<List<'x,T>>),
    Name(Name, Box<List<'x,T>>),
    Art(Art<'x, Box<List<'x,T>>>)
}

pub fn copy<'x,T:'x>(list:List<'x,T>) -> List<'x,T> {
    match list {
        List::Nil         => List::Nil,
        List::Cons(hd,tl) => List::Cons(hd, box copy(*tl)),
        List::Art(art)    => copy(*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box copy(*tl)) ;
            List::Name(nm2, box List::Art(art))
        }
    }
}

pub fn map<'x, T:'x, S:'x>
(f:UAr<'x,T,S>, list:List<'x,T>) -> List<'x,S>
{
    match list {
        List::Nil         => List::Nil,
        List::Cons(hd,tl) => List::Cons((*f).call(hd), box map(f,*tl)),
        // - - - - - boilerplate cases - - - - - -
        List::Art(art)    => map(f,*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box map(f,*tl));
            List::Name(nm2, box List::Art(art))
        }
    }
}

pub fn contract<'x,F,G,T:'x>
(f:&'x F, g:&'x G, list:List<'x,T>) -> List<'x,T>
where F:Fn(&T,&T) -> bool + 'x,
      G:Fn(T,T) -> T + 'x
{
    match list {
        List::Nil => List::Nil,
        List::Cons(hd1, box list) => {
            match list {
                List::Nil => List::Cons(hd1, box List::Nil),
                List::Cons(hd2, tl) => {
                    if (*f)(&hd1,&hd2) {
                        List::Cons((*g)(hd1,hd2), box contract(f,g,*tl))
                    } else {
                        List::Cons(hd1, box contract(f, g, List::Cons(hd2, tl)))
                    } },
                // - - - - - boilerplate cases - - - - - -
                List::Art(art) => contract(f,g,List::Cons(hd1,force(art))),
                List::Name(nm, tl) => {
                    let (nm1,nm2) = fork(nm) ;
                    let art = nart!(nm1, box contract(f,g,List::Cons(hd1,tl))) ;
                    List::Name(nm2, box List::Art(art))
                }
            }
        },
        // - - - - - boilerplate cases - - - - - -
        List::Art(art)    => contract(f,g,*force(art)),
        List::Name(nm,tl) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box contract(f,g,*tl)) ;
            List::Name(nm2, box List::Art(art))
        },
    }
}

pub fn reduce<'x,F:'x,G:'x,T:'x>
(f:&'x F, g:&'x G, list:List<'x,T>) -> Option<T>
where F: Fn(&T,&T) -> bool,
      G: Fn(T, T) -> T,
{
    match list {
        List::Nil => None,
        List::Cons(hd, box list) => {
            match list {
                List::Nil => Some(hd),
                List::Cons(hd2, tl) => {
                    let hd3 = (*g)(hd,hd2) ;
                    let list = contract(f, g, List::Cons(hd3, tl)) ;
                    reduce (f, g, list)
                },
                // - - - - - boilerplate cases - - - - - -
                List::Name(_,tl) => reduce (f, g, *tl),
                List::Art(art)   => reduce (f, g, *force(art)),
            }},
        // - - - - - boilerplate cases - - - - - -
        List::Name(_,tl) => reduce(f, g, *tl),
        List::Art(art)   => reduce(f, g, *force(art)),
    }
}

pub fn merge<'x,T:'x,Ord:'x>
(ord:&'x Ord, list1:List<'x,T>, list2:List<'x,T>) -> List<'x,T>
where Ord: Fn(&T,&T) -> bool
{
    match (list1, list2) {
        (List::Nil, list2) => list2,
        (list1, List::Nil) => list1,
        (List::Cons(hd1,tl1),
         List::Cons(hd2,tl2)) =>
            if (*ord)(&hd1,&hd2) {
                List::Cons(hd1, box merge(ord, *tl1, List::Cons(hd2,tl2)))
            } else {
                List::Cons(hd2, box merge(ord, List::Cons(hd1,tl1), *tl2))
            },
        // - - - - - boilerplate cases - - - - -
        (List::Name(nm,tl), list2) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box merge(ord, *tl, list2)) ;
            List::Name(nm2, box List::Art(art))
        },
        (list1, List::Name(nm,tl)) => {
            let (nm1,nm2) = fork(nm) ;
            let art = nart!(nm1, box merge(ord, list1, *tl)) ;
            List::Name(nm2, box List::Art(art))
        },
        (List::Art(art), list2) => merge(ord, *force(art), list2),
        (list1, List::Art(art)) => merge(ord, list1, *force(art)),
    }
}

pub fn singletons<'x,T:'x>
(list:List<'x,T>) -> List<'x,List<'x,T>>
{
    match list {
        List::Nil => List::Nil,
        List::Cons(hd, tl) => {
            List::Cons( List::Cons(hd, box List::Nil),
                        box singletons(*tl) )
        },
        List::Art(art) => singletons(*force(art)),
        List::Name(nm, tl) => {
            let (nm1, nm2) = fork(nm) ;
            let art = nart!(nm2, box singletons(*tl)) ;
            List::Name(nm1, box List::Art(art))
        }
    }
}

pub fn mergesort<'x,T:'x,Ord:'x>
(ord:&'x Ord, list:List<'x,T>) -> List<'x,T>
where Ord: Fn(&T,&T) -> bool, T:Hash
{
    let c = move |&: list1:&List<'x,T>,list2:&List<'x,T>|
    hash::hash(list1) < hash::hash(list2);

    let m = move |&: list1:List<'x,T>,list2:List<'x,T>|
    merge(ord,list1,list2);

    match reduce(&c, &m, singletons(list)) {
        None => List::Nil,
        Some(list) => list
    }
}

#[test]
pub fn construct_list () {
    let z : List<int> = List::Nil;
    let y : List<int> = List::Cons(1, box z);
    let x : List<int> = List::Art(cell(symbol(format!("two")), box y));
    let l : List<int> = List::Name(symbol(format!("one")), box x);
    println!("constructed list: {}", l);
}

// TODO: Rustup: Fix this:

 /// An iterator over the items in a list.
pub struct ListItems<'x, T: 'x> {
    list: &'x List<'x,T>
}

impl<'x,T> List<'x,T> {
    type Item = &'x T ;
    /// Get an iterator over the items in a list.
    pub fn iter(&'x self) -> ListItems<'x, T> {
        ListItems {
            list: self
        }
    }

}

impl<'x, T:'x> Iterator for ListItems<'x, T> {
    type Item = &'x T ;
    fn next(&mut self) -> Option<&'x T> {
        match *self.list {
            List::Cons(ref hd, ref tl) => {
                self.list = &**tl;
                Some(hd)
            },
            List::Name(_, ref list) => {
                self.list = &**list;
                self.next()
            },
            List::Art(ref art) => {
                self.list = &**force_ref(art);
                self.next()
            },
            List::Nil => None
        }
    }
}

pub enum NameOrContent<T> { Name(Name),Content(T) }

// pub fn clone_iter<'x, 'y, T:'y>
// (iter:&'x mut Iterator) -> List<'y,T>
// where T : Clone,

// {
//     match iter.next() {
//         None => List::Nil,
//         Some(x) => match x {
//             NameOrContent::Content(ref hd) => List::Cons(hd.clone(), box clone_iter(iter)),
//             NameOrContent::Name(ref nm) => {
//                 let (nm1,nm2) = fork(nm.clone());
//                 let rest = clone_iter(iter);
//                 List::Name(nm1, box List::Art( cell(nm2, box rest )))
//             }
//         }
//     }
// }

// pub fn from_iter<'x, 'y, T:'y>
// (iter:&'x mut Iterator) -> List<'y,T>
// where T : Clone
// {
//     match iter.next() {
//         None => List::Nil,
//         Some(x) => match x {
//             NameOrContent::Content(hd) => List::Cons(hd, box from_iter(iter)),
//             NameOrContent::Name(nm) => {
//                 let (nm1,nm2) = fork(nm.clone());
//                 let rest = from_iter(iter);
//                 List::Name(nm1, box List::Art( cell(nm2, box rest )))
//             }
//         }
//     }
// }
<|endoftext|>


<commit_before>use std::fs;
use std::path::{Path,PathBuf};
use std::fmt;

use EntryType::{Directory,File};

#[derive(Debug)]
enum EntryType {
    Directory,
    File,
}

type EntryList = Vec<Entry>;

struct Entry {
    entry_type: EntryType,
    path: PathBuf,
    self_size: u64,
}

impl Entry {
    fn from_metadata(path: PathBuf, metadata: &fs::Metadata) -> Option<Entry> {
        if metadata.is_dir() {
            Some(Entry {path: path, entry_type: Directory, self_size: metadata.len()})
        } else if metadata.is_file() {
            Some(Entry {path: path, entry_type: File, self_size: metadata.len()})
        } else {
            None
        }
    }

    fn for_path(path: PathBuf) -> Option<Entry> {
        match fs::metadata(&path) {
            Ok(metadata) => Entry::from_metadata(path, &metadata),
            _ => None
        }
    }

    fn in_directory(dir: &Path) -> Vec<Entry> {
        match fs::read_dir(dir) {
            Ok(read_dir) => {
                read_dir.filter_map(|child| {
                    match child {
                        Ok(child) => Entry::for_path(child.path()),
                        Err(..) => None,
                    }
                }).collect()
            },
            Err(..) => Vec::new()
        }
    }

    fn children(&self) -> Vec<Entry> {
        // TODO: Cache this
        match self.entry_type {
            Directory => Entry::in_directory(&self.path),
            File => Vec::new(),
        }
    }

    fn size(&self) -> u64 {
        self.self_size + self.descendent_size()
    }

    fn descendent_size(&self) -> u64 {
        self.children().into_iter().
            map(|child| child.size() ).fold(0, |a, n| a + n)
    }

    fn file_name(&self) -> &str {
        // Converting paths to strings might fail. We start by trying to convert the
        // filename. If the path is something like ".", it will fail as there is no "file
        // name" in that path. We fall back to the entire path in that case. If that also
        // fails, we fall back to hardcoded representation.
        let file_name = self.path.file_name().and_then(|s| s.to_str());
        file_name.or_else(|| self.path.to_str()).unwrap_or("(no name)")
    }
}

trait SizeDisplay {
    fn as_size_display(&self) -> String;
}

const KILO: f64 = 1e3 as f64;
const MEGA: f64 = 1e6 as f64;
const GIGA: f64 = 1e9 as f64;

const KILO_CUTOFF: u64 = (0.6 * KILO) as u64;
const MEGA_CUTOFF: u64 = (1.4 * MEGA) as u64;
const GIGA_CUTOFF: u64 = (1.4 * GIGA) as u64;

impl SizeDisplay for u64 {
    fn as_size_display(&self) -> String {
        if *self < KILO_CUTOFF {
            return format!("{} B", *self)
        }

        let (scaled, unit) = match *self {
            KILO_CUTOFF...MEGA_CUTOFF => (*self as f64 / KILO, "kB"),
            MEGA_CUTOFF...GIGA_CUTOFF => (*self as f64 / MEGA, "MB"),
            _ => (*self as f64 / GIGA, "GB"),
        };
        format!("{:.2} {}", scaled, unit)
    }
}

impl fmt::Display for Entry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.file_name(), self.size().as_size_display())
    }
}

fn print_tree(dir_entry: Entry) {
    print_indented_tree(dir_entry, 0);
}

fn print_indented_tree(entry: Entry, indent: usize) {
    println!("{0:1$}{2}", "", indent * 2, entry);
    for child in entry.children() {
        print_indented_tree(child, indent + 1);
    }
}

fn main() {
    let root = Entry::for_path(PathBuf::from(".")).unwrap();
    print_tree(root);
}
<commit_msg>Build children list on construction<commit_after>use std::fs;
use std::path::{Path,PathBuf};
use std::fmt;

struct Entry {
    path: PathBuf,
    self_size: u64,
    children: Vec<Entry>,
}

impl Entry {
    fn from_metadata(path: PathBuf, metadata: &fs::Metadata) -> Option<Entry> {
        let children = if metadata.is_dir() {
            Entry::in_directory(&path)
        } else if metadata.is_file() {
            vec![]
        } else {
            return None;
        };

        Some(Entry {children: children, path: path, self_size: metadata.len()})
    }

    fn for_path(path: PathBuf) -> Option<Entry> {
        match fs::metadata(&path) {
            Ok(metadata) => Entry::from_metadata(path, &metadata),
            _ => None
        }
    }

    fn in_directory(dir: &Path) -> Vec<Entry> {
        match fs::read_dir(dir) {
            Ok(read_dir) => {
                read_dir.filter_map(|child| {
                    match child {
                        Ok(child) => Entry::for_path(child.path()),
                        Err(..) => None,
                    }
                }).collect()
            },
            Err(..) => Vec::new()
        }
    }

    fn size(&self) -> u64 {
        self.self_size + self.descendent_size()
    }

    fn descendent_size(&self) -> u64 {
        self.children.iter().map(|child| child.size()).fold(0, |a, n| a + n)
    }

    fn file_name(&self) -> &str {
        // Converting paths to strings might fail. We start by trying to convert the
        // filename. If the path is something like ".", it will fail as there is no "file
        // name" in that path. We fall back to the entire path in that case. If that also
        // fails, we fall back to hardcoded representation.
        let file_name = self.path.file_name().and_then(|s| s.to_str());
        file_name.or_else(|| self.path.to_str()).unwrap_or("(no name)")
    }
}

trait SizeDisplay {
    fn as_size_display(&self) -> String;
}

const KILO: f64 = 1e3 as f64;
const MEGA: f64 = 1e6 as f64;
const GIGA: f64 = 1e9 as f64;

const KILO_CUTOFF: u64 = (0.6 * KILO) as u64;
const MEGA_CUTOFF: u64 = (1.4 * MEGA) as u64;
const GIGA_CUTOFF: u64 = (1.4 * GIGA) as u64;

impl SizeDisplay for u64 {
    fn as_size_display(&self) -> String {
        if *self < KILO_CUTOFF {
            return format!("{} B", *self)
        }

        let (scaled, unit) = match *self {
            KILO_CUTOFF...MEGA_CUTOFF => (*self as f64 / KILO, "kB"),
            MEGA_CUTOFF...GIGA_CUTOFF => (*self as f64 / MEGA, "MB"),
            _ => (*self as f64 / GIGA, "GB"),
        };
        format!("{:.2} {}", scaled, unit)
    }
}

impl fmt::Display for Entry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.file_name(), self.size().as_size_display())
    }
}

fn print_tree(dir_entry: Entry) {
    print_indented_tree(dir_entry, 0);
}

fn print_indented_tree(entry: Entry, indent: usize) {
    println!("{0:1$}{2}", "", indent * 2, entry);
    for child in entry.children {
        print_indented_tree(child, indent + 1);
    }
}

fn main() {
    let root = Entry::for_path(PathBuf::from(".")).unwrap();
    print_tree(root);
}
<|endoftext|>


<commit_before>extern crate txtpic;
use txtpic::calculate_string_brightness::calculate_string_brightness;

fn main() {
    println!("{}", calculate_string_brightness("@"));
    println!("{}", calculate_string_brightness("%"));
    println!("{}", calculate_string_brightness("#"));
    println!("{}", calculate_string_brightness("x"));
    println!("{}", calculate_string_brightness("+"));
    println!("{}", calculate_string_brightness("="));
    println!("{}", calculate_string_brightness(":"));
    println!("{}", calculate_string_brightness("-"));
    println!("{}", calculate_string_brightness("."));
    println!("{}", calculate_string_brightness(" "));
}
<commit_msg>Use new API in test code<commit_after>extern crate txtpic;
use txtpic::calculate_string_brightness::calculate_string_brightness;

fn main() {
    println!("{}", calculate_string_brightness('M'));
    println!("{}", calculate_string_brightness('@'));
    println!("{}", calculate_string_brightness('%'));
    println!("{}", calculate_string_brightness('#'));
    println!("{}", calculate_string_brightness('x'));
    println!("{}", calculate_string_brightness('+'));
    println!("{}", calculate_string_brightness('='));
    println!("{}", calculate_string_brightness(':'));
    println!("{}", calculate_string_brightness('-'));
    println!("{}", calculate_string_brightness('.'));
    println!("{}", calculate_string_brightness(' '));
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate chrono;
#[macro_use]
extern crate clap;
extern crate rocket_contrib;
extern crate rocket;
#[macro_use]
extern crate serde_derive;
extern crate toml;

mod file_utils;

use chrono::{DateTime, Local};
use rocket::response::NamedFile;
use rocket::State;
use rocket_contrib::Template;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicUsize;

struct Timings {
    launch_time: DateTime<Local>,
    last_ms_from_launch: AtomicUsize, // milliseconds from the launch time
}

#[derive(Deserialize)]
struct Config {
    log_file: PathBuf,
    cooldown_ms: usize, // cooldown in milliseconds
    template_context: TemplateContext,
}

#[derive(Serialize, Deserialize)]
struct TemplateContext {
    title: String,
    question: String,
}

fn load_config_file<T>(path: T) -> Result<Config, io::Error>
    where T: AsRef<Path>
{
    use file_utils::read_file;
    use std::io::ErrorKind;

    let toml_str = read_file(path.as_ref())?;
    toml::from_str(&toml_str)
        .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "Could not parse TOML file"))
}

#[inline]
fn count_ms_from_datetime(dt0: DateTime<Local>) -> usize {
    Local::now().signed_duration_since(dt0).num_milliseconds() as usize
}

#[get("/")]
fn index(conf: State<Config>) -> Template {
    Template::render("index", &conf.template_context)
}

#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).ok()
}

#[post("/feedback/<fb>")]
fn feedback(fb: &str, tmgs: State<Timings>, conf: State<Config>) -> Result<(), io::Error> {
    use file_utils::append_line_to_file;
    use std::io::ErrorKind;
    use std::sync::atomic::Ordering;

    let curr_timestamp = count_ms_from_datetime(tmgs.launch_time);
    let ms_since_last = curr_timestamp - tmgs.last_ms_from_launch.load(Ordering::Relaxed);
    tmgs.last_ms_from_launch.store(curr_timestamp, Ordering::Relaxed);

    if ms_since_last < conf.cooldown_ms {
        return Err(io::Error::new(ErrorKind::Other, "button mashing detected"));
    }

    let fb_int = match fb {
        "so_very_negative" => Ok(-3),
        "very_negative" => Ok(-2),
        "negative" => Ok(-1),
        "neutral" => Ok(0),
        "positive" => Ok(1),
        "very_positive" => Ok(2),
        "so_very_positive" => Ok(3), // Yes! So. Very. Positive.
        _ => Err(io::Error::new(ErrorKind::InvalidInput, "invalid feedback value")),
    }?;

    let line = format!("{},{:>2}", Local::now().to_rfc3339(), fb_int);
    append_line_to_file(&conf.log_file, line)?;

    Ok(())
}

fn main() {
    use clap::{App, Arg};
    use std::process;

    let m = App::new("emoji-feedback")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Emoji feedback server")
        .arg(Arg::with_name("config_file_path")
            .help("Path to the config file")
            .index(1)
            .required(true))
        .get_matches();

    let config_file_path = Path::new(m.value_of("config_file_path").unwrap());

    match load_config_file(&config_file_path) {
        Ok(conf) => {
            let tmgs = Timings {
                launch_time: Local::now(),
                last_ms_from_launch: AtomicUsize::new(0),
            };

            rocket::ignite()
                .mount("/", routes![index, files, feedback])
                .manage(tmgs)
                .manage(conf)
                .launch();
        }
        Err(e) => {
            println!("{}", e);
            process::exit(1);
        }
    }
}
<commit_msg>WIP log file access<commit_after>#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate chrono;
#[macro_use]
extern crate clap;
extern crate rocket_contrib;
extern crate rocket;
#[macro_use]
extern crate serde_derive;
extern crate toml;

mod file_utils;

use chrono::{DateTime, Local};
use rocket::response::NamedFile;
use rocket::State;
use rocket_contrib::Template;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicUsize;

struct Timings {
    launch_time: DateTime<Local>,
    last_ms_from_launch: AtomicUsize, // milliseconds from the launch time
}

#[derive(Deserialize)]
struct Config {
    log_file: PathBuf,
    cooldown_ms: usize, // cooldown in milliseconds
    template_context: TemplateContext,
}

#[derive(Serialize, Deserialize)]
struct TemplateContext {
    title: String,
    question: String,
}

fn load_config_file<T>(path: T) -> Result<Config, io::Error>
    where T: AsRef<Path>
{
    use file_utils::read_file;
    use std::io::ErrorKind;

    let toml_str = read_file(path.as_ref())?;
    toml::from_str(&toml_str)
        .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "Could not parse TOML file"))
}

#[inline]
fn count_ms_from_datetime(dt0: DateTime<Local>) -> usize {
    Local::now().signed_duration_since(dt0).num_milliseconds() as usize
}

#[get("/")]
fn index(conf: State<Config>) -> Template {
    Template::render("index", &conf.template_context)
}

#[get("/log_file")]
fn log_file(conf: State<Config>) -> Option<NamedFile> {
    NamedFile::open(&conf.log_file).ok()
}

#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).ok()
}

#[post("/feedback/<fb>")]
fn feedback(fb: &str, tmgs: State<Timings>, conf: State<Config>) -> Result<(), io::Error> {
    use file_utils::append_line_to_file;
    use std::io::ErrorKind;
    use std::sync::atomic::Ordering;

    let curr_timestamp = count_ms_from_datetime(tmgs.launch_time);
    let ms_since_last = curr_timestamp - tmgs.last_ms_from_launch.load(Ordering::Relaxed);
    tmgs.last_ms_from_launch.store(curr_timestamp, Ordering::Relaxed);

    if ms_since_last < conf.cooldown_ms {
        return Err(io::Error::new(ErrorKind::Other, "button mashing detected"));
    }

    let fb_int = match fb {
        "so_very_negative" => Ok(-3),
        "very_negative" => Ok(-2),
        "negative" => Ok(-1),
        "neutral" => Ok(0),
        "positive" => Ok(1),
        "very_positive" => Ok(2),
        "so_very_positive" => Ok(3), // Yes! So. Very. Positive.
        _ => Err(io::Error::new(ErrorKind::InvalidInput, "invalid feedback value")),
    }?;

    let line = format!("{},{:>2}", Local::now().to_rfc3339(), fb_int);
    append_line_to_file(&conf.log_file, line)?;

    Ok(())
}

fn main() {
    use clap::{App, Arg};
    use std::process;

    let m = App::new("emoji-feedback")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Emoji feedback server")
        .arg(Arg::with_name("config_file_path")
            .help("Path to the config file")
            .index(1)
            .required(true))
        .get_matches();

    let config_file_path = Path::new(m.value_of("config_file_path").unwrap());

    match load_config_file(&config_file_path) {
        Ok(conf) => {
            let tmgs = Timings {
                launch_time: Local::now(),
                last_ms_from_launch: AtomicUsize::new(0),
            };

            rocket::ignite()
                .mount("/", routes![index, files, log_file, feedback])
                .manage(tmgs)
                .manage(conf)
                .launch();
        }
        Err(e) => {
            println!("{}", e);
            process::exit(1);
        }
    }
}
<|endoftext|>


<commit_before>#![feature(globs)]

extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate serialize;

use rgtk::*;
use std::collections::HashSet;
use std::io::fs;
use std::io::fs::PathExtensions;

mod projects;
mod ui;
mod utils;

mod ffi {
    pub use libc::{c_int, c_uchar, c_void};
    pub use libc::funcs::posix88::unistd::{close, pipe, read, write};
    pub use libc::types::os::arch::c95::size_t;

    extern "C" {
        pub fn fork () -> c_int;
        pub fn kill (pid: c_int, sig: c_int);
    }
}

fn gui_main(
    pty: &mut gtk::VtePty,
    read_fd: ffi::c_int,
    write_fd: ffi::c_int,
    pid: ffi::c_int)
{
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;

    // create the window

    let title = format!("SolidOak {}.{}.{}",
                        option_env!("CARGO_PKG_VERSION_MAJOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_MINOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_PATCH").unwrap());
    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title(title.as_slice());
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(|_| {
        unsafe {
            ffi::close(read_fd);
            ffi::close(write_fd);
            ffi::kill(pid, 15);
        }
        gtk::main_quit();
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut project_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    project_buttons.set_size_request(-1, -1);
    project_buttons.add(&new_button);
    project_buttons.add(&import_button);
    project_buttons.add(&rename_button);
    project_buttons.add(&remove_button);

    let mut project_tree = gtk::TreeView::new().unwrap();
    let selection = project_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    project_tree.set_model(&model);
    project_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&project_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    project_tree.append_column(&column);

    let mut project_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    project_pane.set_size_request(-1, -1);
    project_pane.pack_start(&project_buttons, false, true, 0);
    project_pane.pack_start(&scroll_pane, true, true, 0);

    let mut editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);
    editor_pane.set_pty(pty);
    editor_pane.watch_child(pid);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_buttons.set_size_request(-1, -1);
    build_buttons.add(&run_button);
    build_buttons.add(&build_button);
    build_buttons.add(&test_button);
    build_buttons.add(&clean_button);
    build_buttons.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_buttons, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, false, true, 0);
    content.pack_start(&build_pane, true, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&project_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
    };

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::new_project(&mut state, &mut project_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::import_project(&mut state, &mut project_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::rename_file(&mut state);
    }));
    remove_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(|| {
        ::projects::save_selection(&mut state);
    }));
    project_tree.connect(gtk::signals::RowCollapsed::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    project_tree.connect(gtk::signals::RowExpanded::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // show the window

    window.show_all();
    gtk::main();
}

extern "C" fn nvim_init(read_fs: i32, write_fs: i32) {
    let mut ch = neovim::Channel::new_with_fds(read_fs, write_fs);
    ch.subscribe("test");

    unsafe {
        let msg = "Hello, world!";
        let msg_c = msg.to_c_str();
        let msg_ptr = msg_c.as_ptr() as *const ffi::c_void;
        ffi::write(write_fs, msg_ptr, msg_c.len() as ffi::size_t);
    }
}

fn main() {
    // create data dir and config file
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::mkdir(&data_dir, ::std::io::USER_DIR) {
            Ok(_) => {
                // TODO: copy all the vim files into the path
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }
    std::os::setenv("VIM", data_dir.as_str().unwrap());

    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match std::io::File::create(&config_file).write(::utils::CONFIG_CONTENT.as_bytes()) {
            Ok(_) => {},
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

    // takes care of piping stdin/stdout between the gui and nvim
    let mut pty = gtk::VtePty::new().unwrap();

    // two pairs of anonymous pipes for msgpack-rpc between the gui and nvim
    let mut nvim_from_gui : [ffi::c_int, ..2] = [0, ..2];
    let mut gui_from_nvim : [ffi::c_int, ..2] = [0, ..2];
    unsafe {
        ffi::pipe(nvim_from_gui.as_mut_ptr());
        ffi::pipe(gui_from_nvim.as_mut_ptr());
    };

    // split into two processes
    let pid = unsafe { ffi::fork() };

    if pid > 0 { // the gui process
        // listen for messages from nvim
        spawn(proc() {
            let mut buf : [ffi::c_uchar, ..100] = [0, ..100];
            unsafe {
                loop {
                    let buf_ptr = buf.as_mut_ptr() as *mut ffi::c_void;
                    let n = ffi::read(gui_from_nvim[0], buf_ptr, 100);
                    if n < 0 {
                        break;
                    } else if n > 0 {
                        let msg = std::str::from_utf8(buf.slice_to(n as uint)).unwrap();
                        println!("Received: {}", msg);
                    }
                }
            }
        });

        // start the gui
        gui_main(&mut pty, gui_from_nvim[0], gui_from_nvim[1], pid);
    } else { // the nvim process
        // prepare this process to be piped into the gui
        pty.child_setup();

        // start nvim
        let mut args = std::os::args().clone();
        args.push_all(&["-u".to_string(), config_file.as_str().unwrap().to_string()]);
        neovim::run_with_callback(args, Some(nvim_init), nvim_from_gui[0], gui_from_nvim[1]);
    }
}
<commit_msg>Make sylistic changes<commit_after>#![feature(globs)]

extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate serialize;

use rgtk::*;
use std::collections::HashSet;
use std::io::fs;
use std::io::fs::PathExtensions;

mod projects;
mod ui;
mod utils;

mod ffi {
    pub use libc::{c_int, c_uchar, c_void};
    pub use libc::funcs::posix88::unistd::{close, pipe, read, write};
    pub use libc::types::os::arch::c95::size_t;

    extern "C" {
        pub fn fork () -> c_int;
        pub fn kill (pid: c_int, sig: c_int);
    }
}

fn gui_main(
    pty: &mut gtk::VtePty,
    read_fd: ffi::c_int,
    write_fd: ffi::c_int,
    pid: ffi::c_int)
{
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;

    // create the window

    let title = format!("SolidOak {}.{}.{}",
                        option_env!("CARGO_PKG_VERSION_MAJOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_MINOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_PATCH").unwrap());
    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title(title.as_slice());
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(|_| {
        unsafe {
            ffi::close(read_fd);
            ffi::close(write_fd);
            ffi::kill(pid, 15);
        }
        gtk::main_quit();
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut project_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    project_buttons.set_size_request(-1, -1);
    project_buttons.add(&new_button);
    project_buttons.add(&import_button);
    project_buttons.add(&rename_button);
    project_buttons.add(&remove_button);

    let mut project_tree = gtk::TreeView::new().unwrap();
    let selection = project_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    project_tree.set_model(&model);
    project_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&project_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    project_tree.append_column(&column);

    let mut project_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    project_pane.set_size_request(-1, -1);
    project_pane.pack_start(&project_buttons, false, true, 0);
    project_pane.pack_start(&scroll_pane, true, true, 0);

    let mut editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);
    editor_pane.set_pty(pty);
    editor_pane.watch_child(pid);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_buttons.set_size_request(-1, -1);
    build_buttons.add(&run_button);
    build_buttons.add(&build_button);
    build_buttons.add(&test_button);
    build_buttons.add(&clean_button);
    build_buttons.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_buttons, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, false, true, 0);
    content.pack_start(&build_pane, true, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&project_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
    };

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::new_project(&mut state, &mut project_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::import_project(&mut state, &mut project_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::rename_file(&mut state);
    }));
    remove_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(|| {
        ::projects::save_selection(&mut state);
    }));
    project_tree.connect(gtk::signals::RowCollapsed::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    project_tree.connect(gtk::signals::RowExpanded::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // show the window

    window.show_all();
    gtk::main();
}

extern "C" fn nvim_init(read_fs: i32, write_fs: i32) {
    let mut ch = neovim::Channel::new_with_fds(read_fs, write_fs);
    ch.subscribe("test");

    unsafe {
        let msg = "Hello, world!";
        let msg_c = msg.to_c_str();
        let msg_ptr = msg_c.as_ptr() as *const ffi::c_void;
        ffi::write(write_fs, msg_ptr, msg_c.len() as ffi::size_t);
    }
}

fn main() {
    // create data dir
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::mkdir(&data_dir, ::std::io::USER_DIR) {
            Ok(_) => {
                // TODO: copy all the vim files into the path
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }
    ::std::os::setenv("VIM", data_dir.as_str().unwrap());

    // create config file
    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match ::std::io::File::create(&config_file).write(::utils::CONFIG_CONTENT.as_bytes()) {
            Ok(_) => {},
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

    // takes care of piping stdin/stdout between the gui and nvim
    let mut pty = gtk::VtePty::new().unwrap();

    // two pairs of anonymous pipes for msgpack-rpc between the gui and nvim
    let mut nvim_from_gui : [ffi::c_int, ..2] = [0, ..2];
    let mut gui_from_nvim : [ffi::c_int, ..2] = [0, ..2];
    unsafe {
        ffi::pipe(nvim_from_gui.as_mut_ptr());
        ffi::pipe(gui_from_nvim.as_mut_ptr());
    };

    // split into two processes
    let pid = unsafe { ffi::fork() };

    if pid > 0 { // the gui process
        // listen for messages from nvim
        spawn(proc() {
            let mut buf : [ffi::c_uchar, ..100] = [0, ..100];
            unsafe {
                loop {
                    let buf_ptr = buf.as_mut_ptr() as *mut ffi::c_void;
                    let n = ffi::read(gui_from_nvim[0], buf_ptr, 100);
                    if n < 0 {
                        break;
                    } else if n > 0 {
                        let msg = ::std::str::from_utf8(buf.slice_to(n as uint)).unwrap();
                        println!("Received: {}", msg);
                    }
                }
            }
        });

        // start the gui
        gui_main(&mut pty, gui_from_nvim[0], gui_from_nvim[1], pid);
    } else { // the nvim process
        // prepare this process to be piped into the gui
        pty.child_setup();

        // start nvim
        let mut args = ::std::os::args().clone();
        args.push_all(&["-u".to_string(), config_file.as_str().unwrap().to_string()]);
        neovim::run_with_callback(args, Some(nvim_init), nvim_from_gui[0], gui_from_nvim[1]);
    }
}
<|endoftext|>


<commit_before>#![forbid(unsafe_code)]

pub mod api;
pub mod db;
pub mod importer;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use std::{mem, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};

use axum::{
    extract::{Extension, Path, Query},
    http::StatusCode,
    routing::{get, post, put},
    Json, Router,
};
use clap::Parser;
use futures_util::stream::Stream;
use moka::sync::Cache;
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};
use shakmaty::{
    san::{San, SanPlus},
    uci::Uci,
    variant::VariantPosition,
    Color,
};
use tikv_jemallocator::Jemalloc;
use tokio::sync::watch;
use tower::ServiceBuilder;

use crate::{
    api::{
        Error, ExplorerGame, ExplorerGameWithUci, ExplorerMove, ExplorerResponse, LichessQuery,
        Limits, MastersQuery, NdJson, PlayPosition, PlayerQuery, PlayerQueryFilter,
    },
    db::{Database, LichessDatabase},
    importer::{LichessGameImport, LichessImporter, MastersImporter},
    indexer::{IndexerOpt, IndexerStub},
    model::{GameId, KeyBuilder, KeyPrefix, MastersGame, MastersGameWithId, PreparedMove, UserId},
    opening::{Opening, Openings},
    util::DedupStreamExt as _,
};

#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

#[derive(Parser)]
struct Opt {
    /// Binding address. Note that administrative endpoints must be protected
    /// using a reverse proxy.
    #[clap(long, default_value = "127.0.0.1:9002")]
    bind: SocketAddr,
    /// Path to RocksDB database
    #[clap(long, default_value = "_db")]
    db: PathBuf,
    /// Allow access from all origins.
    #[clap(long)]
    cors: bool,
    #[clap(flatten)]
    indexer: IndexerOpt,
}

type ExplorerCache<T> = Cache<T, Result<Json<ExplorerResponse>, Error>>;

#[tokio::main]
async fn main() {
    env_logger::Builder::from_env(
        env_logger::Env::new()
            .filter("EXPLORER_LOG")
            .write_style("EXPLORER_LOG_STYLE"),
    )
    .format_timestamp(None)
    .format_module_path(false)
    .format_target(false)
    .init();

    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::build_table()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handles) = IndexerStub::spawn(Arc::clone(&db), opt.indexer);
    let masters_importer = MastersImporter::new(Arc::clone(&db));
    let lichess_importer = LichessImporter::new(Arc::clone(&db));

    let lichess_cache: ExplorerCache<LichessQuery> = Cache::builder()
        .max_capacity(1000)
        .time_to_live(Duration::from_secs(5 * 60))
        .build();

    let masters_cache: ExplorerCache<MastersQuery> = Cache::builder()
        .max_capacity(1000)
        .time_to_live(Duration::from_secs(5 * 60))
        .build();

    let app = Router::new()
        .route("/monitor/cf/:cf/:prop", get(cf_prop))
        .route("/monitor/db/:prop", get(db_prop))
        .route("/monitor/indexing", get(num_indexing))
        .route("/compact", post(compact))
        .route("/import/masters", put(masters_import))
        .route("/import/lichess", put(lichess_import))
        .route("/masters/pgn/:id", get(masters_pgn))
        .route("/masters", get(masters))
        .route("/lichess", get(lichess))
        .route("/player", get(player))
        .route("/master/pgn/:id", get(masters_pgn)) // bc
        .route("/master", get(masters)) // bc
        .route("/personal", get(player)) // bc
        .layer(
            ServiceBuilder::new()
                .layer(Extension(openings))
                .layer(Extension(db))
                .layer(Extension(masters_cache))
                .layer(Extension(lichess_cache))
                .layer(Extension(masters_importer))
                .layer(Extension(lichess_importer))
                .layer(Extension(indexer)),
        );

    let app = if opt.cors {
        app.layer(
            tower_http::set_header::SetResponseHeaderLayer::if_not_present(
                axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
                axum::http::HeaderValue::from_static("*"),
            ),
        )
    } else {
        app
    };

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .await
        .expect("bind");

    for join_handle in join_handles {
        join_handle.await.expect("indexer");
    }
}

#[derive(Deserialize)]
struct ColumnFamilyProp {
    cf: String,
    prop: String,
}

async fn cf_prop(
    Path(path): Path<ColumnFamilyProp>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<String, StatusCode> {
    db.inner
        .cf_handle(&path.cf)
        .and_then(|cf| {
            db.inner
                .property_value_cf(cf, &path.prop)
                .expect("property value")
        })
        .ok_or(StatusCode::NOT_FOUND)
}

async fn db_prop(
    Path(prop): Path<String>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<String, StatusCode> {
    db.inner
        .property_value(&prop)
        .expect("property value")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn num_indexing(Extension(indexer): Extension<IndexerStub>) -> String {
    indexer.num_indexing().await.to_string()
}

async fn compact(Extension(db): Extension<Arc<Database>>) {
    db.compact();
}

fn finalize_lichess_moves(
    moves: Vec<PreparedMove>,
    pos: &VariantPosition,
    lichess_db: &LichessDatabase,
) -> Vec<ExplorerMove> {
    moves
        .into_iter()
        .map(|p| ExplorerMove {
            stats: p.stats,
            san: p.uci.to_move(pos).map_or(
                SanPlus {
                    san: San::Null,
                    suffix: None,
                },
                |m| SanPlus::from_move(pos.clone(), &m),
            ),
            uci: p.uci,
            average_rating: p.average_rating,
            average_opponent_rating: p.average_opponent_rating,
            performance: p.performance,
            game: p.game.and_then(|id| {
                lichess_db
                    .game(id)
                    .expect("get game")
                    .map(|info| ExplorerGame::from_lichess(id, info))
            }),
        })
        .collect()
}

fn finalize_lichess_games(
    games: Vec<(Uci, GameId)>,
    lichess_db: &LichessDatabase,
) -> Vec<ExplorerGameWithUci> {
    lichess_db
        .games(games.iter().map(|(_, id)| *id))
        .expect("get games")
        .into_iter()
        .zip(games.into_iter())
        .filter_map(|(info, (uci, id))| {
            info.map(|info| ExplorerGameWithUci {
                uci,
                row: ExplorerGame::from_lichess(id, info),
            })
        })
        .collect()
}

struct PlayerStreamState {
    indexing: Option<watch::Receiver<()>>,
    key: KeyPrefix,
    db: Arc<Database>,
    color: Color,
    filter: PlayerQueryFilter,
    limits: Limits,
    pos: VariantPosition,
    opening: Option<&'static Opening>,
    first: bool,
    done: bool,
}

async fn player(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PlayerQuery>,
) -> Result<NdJson<impl Stream<Item = ExplorerResponse>>, Error> {
    let player = UserId::from(query.player);
    let indexing = indexer.index_player(&player).await;
    let PlayPosition {
        variant,
        pos,
        opening,
    } = query.play.position(openings)?;
    let key = KeyBuilder::player(&player, query.color).with_zobrist(variant, pos.zobrist_hash());

    let state = PlayerStreamState {
        color: query.color,
        filter: query.filter,
        limits: query.limits,
        db,
        indexing,
        opening,
        key,
        pos: pos.into_inner(),
        first: true,
        done: false,
    };

    Ok(NdJson(futures_util::stream::unfold(
        state,
        |mut state| async move {
            if state.done {
                return None;
            }

            let first = mem::replace(&mut state.first, false);
            state.done = match state.indexing {
                Some(ref mut indexing) => {
                    tokio::select! {
                        _ = indexing.changed() => true,
                        _ = tokio::time::sleep(Duration::from_millis(if first { 0 } else { 1000 })) => false,
                    }
                }
                None => true,
            };

            let lichess_db = state.db.lichess();
            let filtered = lichess_db
                .read_player(&state.key, state.filter.since, state.filter.until)
                .expect("read player")
                .prepare(state.color, &state.filter, &state.limits);

            Some((
                ExplorerResponse {
                    total: filtered.total,
                    moves: finalize_lichess_moves(filtered.moves, &state.pos, &lichess_db),
                    recent_games: Some(finalize_lichess_games(filtered.recent_games, &lichess_db)),
                    top_games: None,
                    opening: state.opening,
                },
                state,
            ))
        },
    ).dedup_by_key(|res| res.total.total())))
}

async fn masters_import(
    Json(body): Json<MastersGameWithId>,
    Extension(importer): Extension<MastersImporter>,
) -> Result<(), Error> {
    importer.import(body).await
}

#[serde_as]
#[derive(Deserialize)]
struct MastersGameId(#[serde_as(as = "DisplayFromStr")] GameId);

async fn masters_pgn(
    Path(MastersGameId(id)): Path<MastersGameId>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<MastersGame, StatusCode> {
    match db.masters().game(id).expect("get masters game") {
        Some(game) => Ok(game),
        None => Err(StatusCode::NOT_FOUND),
    }
}

async fn masters(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(masters_cache): Extension<ExplorerCache<MastersQuery>>,
    Query(query): Query<MastersQuery>,
) -> Result<Json<ExplorerResponse>, Error> {
    masters_cache.get_with(query.clone(), || {
        let PlayPosition {
            variant,
            pos,
            opening,
        } = query.play.position(openings)?;
        let key = KeyBuilder::masters().with_zobrist(variant, pos.zobrist_hash());
        let masters_db = db.masters();
        let entry = masters_db
            .read(key, query.since, query.until)
            .expect("get masters")
            .prepare(&query.limits);

        Ok(Json(ExplorerResponse {
            total: entry.total,
            moves: entry
                .moves
                .into_iter()
                .map(|p| ExplorerMove {
                    san: p.uci.to_move(&pos).map_or(
                        SanPlus {
                            san: San::Null,
                            suffix: None,
                        },
                        |m| SanPlus::from_move(pos.clone(), &m),
                    ),
                    uci: p.uci,
                    average_rating: p.average_rating,
                    average_opponent_rating: p.average_opponent_rating,
                    performance: p.performance,
                    stats: p.stats,
                    game: p.game.and_then(|id| {
                        masters_db
                            .game(id)
                            .expect("get masters game")
                            .map(|info| ExplorerGame::from_masters(id, info))
                    }),
                })
                .collect(),
            top_games: Some(
                masters_db
                    .games(entry.top_games.iter().map(|(_, id)| *id))
                    .expect("get masters games")
                    .into_iter()
                    .zip(entry.top_games.into_iter())
                    .filter_map(|(info, (uci, id))| {
                        info.map(|info| ExplorerGameWithUci {
                            uci: uci.clone(),
                            row: ExplorerGame::from_masters(id, info),
                        })
                    })
                    .collect(),
            ),
            opening,
            recent_games: None,
        }))
    })
}

async fn lichess_import(
    Json(body): Json<Vec<LichessGameImport>>,
    Extension(importer): Extension<LichessImporter>,
) -> Result<(), Error> {
    for game in body {
        importer.import(game).await?;
    }
    Ok(())
}

async fn lichess(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(lichess_cache): Extension<ExplorerCache<LichessQuery>>,
    Query(query): Query<LichessQuery>,
) -> Result<Json<ExplorerResponse>, Error> {
    lichess_cache.get_with(query.clone(), || {
        let PlayPosition {
            variant,
            pos,
            opening,
        } = query.play.position(openings)?;
        let key = KeyBuilder::lichess().with_zobrist(variant, pos.zobrist_hash());
        let lichess_db = db.lichess();
        let filtered = lichess_db
            .read_lichess(&key, query.filter.since, query.filter.until)
            .expect("get lichess")
            .prepare(&query.filter, &query.limits);

        Ok(Json(ExplorerResponse {
            total: filtered.total,
            moves: finalize_lichess_moves(filtered.moves, pos.as_inner(), &lichess_db),
            recent_games: Some(finalize_lichess_games(filtered.recent_games, &lichess_db)),
            top_games: Some(finalize_lichess_games(filtered.top_games, &lichess_db)),
            opening,
        }))
    })
}
<commit_msg>add --cache-size flag<commit_after>#![forbid(unsafe_code)]

pub mod api;
pub mod db;
pub mod importer;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use std::{mem, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};

use axum::{
    extract::{Extension, Path, Query},
    http::StatusCode,
    routing::{get, post, put},
    Json, Router,
};
use clap::Parser;
use futures_util::stream::Stream;
use moka::sync::Cache;
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};
use shakmaty::{
    san::{San, SanPlus},
    uci::Uci,
    variant::VariantPosition,
    Color,
};
use tikv_jemallocator::Jemalloc;
use tokio::sync::watch;
use tower::ServiceBuilder;

use crate::{
    api::{
        Error, ExplorerGame, ExplorerGameWithUci, ExplorerMove, ExplorerResponse, LichessQuery,
        Limits, MastersQuery, NdJson, PlayPosition, PlayerQuery, PlayerQueryFilter,
    },
    db::{Database, LichessDatabase},
    importer::{LichessGameImport, LichessImporter, MastersImporter},
    indexer::{IndexerOpt, IndexerStub},
    model::{GameId, KeyBuilder, KeyPrefix, MastersGame, MastersGameWithId, PreparedMove, UserId},
    opening::{Opening, Openings},
    util::DedupStreamExt as _,
};

#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

#[derive(Parser)]
struct Opt {
    /// Binding address. Note that administrative endpoints must be protected
    /// using a reverse proxy.
    #[clap(long, default_value = "127.0.0.1:9002")]
    bind: SocketAddr,
    /// Path to RocksDB database
    #[clap(long, default_value = "_db")]
    db: PathBuf,
    /// Allow access from all origins.
    #[clap(long)]
    cors: bool,
    #[clap(flatten)]
    indexer: IndexerOpt,
    #[clap(long, default_value = "2000")]
    cache_size: u64,
}

type ExplorerCache<T> = Cache<T, Result<Json<ExplorerResponse>, Error>>;

#[tokio::main]
async fn main() {
    env_logger::Builder::from_env(
        env_logger::Env::new()
            .filter("EXPLORER_LOG")
            .write_style("EXPLORER_LOG_STYLE"),
    )
    .format_timestamp(None)
    .format_module_path(false)
    .format_target(false)
    .init();

    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::build_table()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handles) = IndexerStub::spawn(Arc::clone(&db), opt.indexer);
    let masters_importer = MastersImporter::new(Arc::clone(&db));
    let lichess_importer = LichessImporter::new(Arc::clone(&db));

    let lichess_cache: ExplorerCache<LichessQuery> = Cache::builder()
        .max_capacity(opt.cache_size)
        .time_to_live(Duration::from_secs(5 * 60))
        .build();

    let masters_cache: ExplorerCache<MastersQuery> = Cache::builder()
        .max_capacity(opt.cache_size)
        .time_to_live(Duration::from_secs(5 * 60))
        .build();

    let app = Router::new()
        .route("/monitor/cf/:cf/:prop", get(cf_prop))
        .route("/monitor/db/:prop", get(db_prop))
        .route("/monitor/indexing", get(num_indexing))
        .route("/compact", post(compact))
        .route("/import/masters", put(masters_import))
        .route("/import/lichess", put(lichess_import))
        .route("/masters/pgn/:id", get(masters_pgn))
        .route("/masters", get(masters))
        .route("/lichess", get(lichess))
        .route("/player", get(player))
        .route("/master/pgn/:id", get(masters_pgn)) // bc
        .route("/master", get(masters)) // bc
        .route("/personal", get(player)) // bc
        .layer(
            ServiceBuilder::new()
                .layer(Extension(openings))
                .layer(Extension(db))
                .layer(Extension(masters_cache))
                .layer(Extension(lichess_cache))
                .layer(Extension(masters_importer))
                .layer(Extension(lichess_importer))
                .layer(Extension(indexer)),
        );

    let app = if opt.cors {
        app.layer(
            tower_http::set_header::SetResponseHeaderLayer::if_not_present(
                axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
                axum::http::HeaderValue::from_static("*"),
            ),
        )
    } else {
        app
    };

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .await
        .expect("bind");

    for join_handle in join_handles {
        join_handle.await.expect("indexer");
    }
}

#[derive(Deserialize)]
struct ColumnFamilyProp {
    cf: String,
    prop: String,
}

async fn cf_prop(
    Path(path): Path<ColumnFamilyProp>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<String, StatusCode> {
    db.inner
        .cf_handle(&path.cf)
        .and_then(|cf| {
            db.inner
                .property_value_cf(cf, &path.prop)
                .expect("property value")
        })
        .ok_or(StatusCode::NOT_FOUND)
}

async fn db_prop(
    Path(prop): Path<String>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<String, StatusCode> {
    db.inner
        .property_value(&prop)
        .expect("property value")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn num_indexing(Extension(indexer): Extension<IndexerStub>) -> String {
    indexer.num_indexing().await.to_string()
}

async fn compact(Extension(db): Extension<Arc<Database>>) {
    db.compact();
}

fn finalize_lichess_moves(
    moves: Vec<PreparedMove>,
    pos: &VariantPosition,
    lichess_db: &LichessDatabase,
) -> Vec<ExplorerMove> {
    moves
        .into_iter()
        .map(|p| ExplorerMove {
            stats: p.stats,
            san: p.uci.to_move(pos).map_or(
                SanPlus {
                    san: San::Null,
                    suffix: None,
                },
                |m| SanPlus::from_move(pos.clone(), &m),
            ),
            uci: p.uci,
            average_rating: p.average_rating,
            average_opponent_rating: p.average_opponent_rating,
            performance: p.performance,
            game: p.game.and_then(|id| {
                lichess_db
                    .game(id)
                    .expect("get game")
                    .map(|info| ExplorerGame::from_lichess(id, info))
            }),
        })
        .collect()
}

fn finalize_lichess_games(
    games: Vec<(Uci, GameId)>,
    lichess_db: &LichessDatabase,
) -> Vec<ExplorerGameWithUci> {
    lichess_db
        .games(games.iter().map(|(_, id)| *id))
        .expect("get games")
        .into_iter()
        .zip(games.into_iter())
        .filter_map(|(info, (uci, id))| {
            info.map(|info| ExplorerGameWithUci {
                uci,
                row: ExplorerGame::from_lichess(id, info),
            })
        })
        .collect()
}

struct PlayerStreamState {
    indexing: Option<watch::Receiver<()>>,
    key: KeyPrefix,
    db: Arc<Database>,
    color: Color,
    filter: PlayerQueryFilter,
    limits: Limits,
    pos: VariantPosition,
    opening: Option<&'static Opening>,
    first: bool,
    done: bool,
}

async fn player(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PlayerQuery>,
) -> Result<NdJson<impl Stream<Item = ExplorerResponse>>, Error> {
    let player = UserId::from(query.player);
    let indexing = indexer.index_player(&player).await;
    let PlayPosition {
        variant,
        pos,
        opening,
    } = query.play.position(openings)?;
    let key = KeyBuilder::player(&player, query.color).with_zobrist(variant, pos.zobrist_hash());

    let state = PlayerStreamState {
        color: query.color,
        filter: query.filter,
        limits: query.limits,
        db,
        indexing,
        opening,
        key,
        pos: pos.into_inner(),
        first: true,
        done: false,
    };

    Ok(NdJson(futures_util::stream::unfold(
        state,
        |mut state| async move {
            if state.done {
                return None;
            }

            let first = mem::replace(&mut state.first, false);
            state.done = match state.indexing {
                Some(ref mut indexing) => {
                    tokio::select! {
                        _ = indexing.changed() => true,
                        _ = tokio::time::sleep(Duration::from_millis(if first { 0 } else { 1000 })) => false,
                    }
                }
                None => true,
            };

            let lichess_db = state.db.lichess();
            let filtered = lichess_db
                .read_player(&state.key, state.filter.since, state.filter.until)
                .expect("read player")
                .prepare(state.color, &state.filter, &state.limits);

            Some((
                ExplorerResponse {
                    total: filtered.total,
                    moves: finalize_lichess_moves(filtered.moves, &state.pos, &lichess_db),
                    recent_games: Some(finalize_lichess_games(filtered.recent_games, &lichess_db)),
                    top_games: None,
                    opening: state.opening,
                },
                state,
            ))
        },
    ).dedup_by_key(|res| res.total.total())))
}

async fn masters_import(
    Json(body): Json<MastersGameWithId>,
    Extension(importer): Extension<MastersImporter>,
) -> Result<(), Error> {
    importer.import(body).await
}

#[serde_as]
#[derive(Deserialize)]
struct MastersGameId(#[serde_as(as = "DisplayFromStr")] GameId);

async fn masters_pgn(
    Path(MastersGameId(id)): Path<MastersGameId>,
    Extension(db): Extension<Arc<Database>>,
) -> Result<MastersGame, StatusCode> {
    match db.masters().game(id).expect("get masters game") {
        Some(game) => Ok(game),
        None => Err(StatusCode::NOT_FOUND),
    }
}

async fn masters(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(masters_cache): Extension<ExplorerCache<MastersQuery>>,
    Query(query): Query<MastersQuery>,
) -> Result<Json<ExplorerResponse>, Error> {
    masters_cache.get_with(query.clone(), || {
        let PlayPosition {
            variant,
            pos,
            opening,
        } = query.play.position(openings)?;
        let key = KeyBuilder::masters().with_zobrist(variant, pos.zobrist_hash());
        let masters_db = db.masters();
        let entry = masters_db
            .read(key, query.since, query.until)
            .expect("get masters")
            .prepare(&query.limits);

        Ok(Json(ExplorerResponse {
            total: entry.total,
            moves: entry
                .moves
                .into_iter()
                .map(|p| ExplorerMove {
                    san: p.uci.to_move(&pos).map_or(
                        SanPlus {
                            san: San::Null,
                            suffix: None,
                        },
                        |m| SanPlus::from_move(pos.clone(), &m),
                    ),
                    uci: p.uci,
                    average_rating: p.average_rating,
                    average_opponent_rating: p.average_opponent_rating,
                    performance: p.performance,
                    stats: p.stats,
                    game: p.game.and_then(|id| {
                        masters_db
                            .game(id)
                            .expect("get masters game")
                            .map(|info| ExplorerGame::from_masters(id, info))
                    }),
                })
                .collect(),
            top_games: Some(
                masters_db
                    .games(entry.top_games.iter().map(|(_, id)| *id))
                    .expect("get masters games")
                    .into_iter()
                    .zip(entry.top_games.into_iter())
                    .filter_map(|(info, (uci, id))| {
                        info.map(|info| ExplorerGameWithUci {
                            uci: uci.clone(),
                            row: ExplorerGame::from_masters(id, info),
                        })
                    })
                    .collect(),
            ),
            opening,
            recent_games: None,
        }))
    })
}

async fn lichess_import(
    Json(body): Json<Vec<LichessGameImport>>,
    Extension(importer): Extension<LichessImporter>,
) -> Result<(), Error> {
    for game in body {
        importer.import(game).await?;
    }
    Ok(())
}

async fn lichess(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(lichess_cache): Extension<ExplorerCache<LichessQuery>>,
    Query(query): Query<LichessQuery>,
) -> Result<Json<ExplorerResponse>, Error> {
    lichess_cache.get_with(query.clone(), || {
        let PlayPosition {
            variant,
            pos,
            opening,
        } = query.play.position(openings)?;
        let key = KeyBuilder::lichess().with_zobrist(variant, pos.zobrist_hash());
        let lichess_db = db.lichess();
        let filtered = lichess_db
            .read_lichess(&key, query.filter.since, query.filter.until)
            .expect("get lichess")
            .prepare(&query.filter, &query.limits);

        Ok(Json(ExplorerResponse {
            total: filtered.total,
            moves: finalize_lichess_moves(filtered.moves, pos.as_inner(), &lichess_db),
            recent_games: Some(finalize_lichess_games(filtered.recent_games, &lichess_db)),
            top_games: Some(finalize_lichess_games(filtered.top_games, &lichess_db)),
            opening,
        }))
    })
}
<|endoftext|>