<commit_before>extern crate hyper;
extern crate hyper_rustls;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate scraper;
extern crate chrono;
extern crate dotenv;

use hyper::Client;

use scraper::{Html, Selector};

use chrono::{UTC, TimeZone};

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::env;

const NEWS_URL: &'static str = "http://na.finalfantasyxiv.com/lodestone/news/";

struct NewsScraper {
  client: Client,
  database: NewsDatabase
}

#[derive(Debug, Default, Serialize, Deserialize)]
struct NewsDatabase {
  items: HashMap<String, NewsItem>
}

#[derive(Debug, Serialize, Deserialize)]
struct NewsItem {
  title: String,
  url: String,
  kind: NewsKind,
  time: i64,
  tag: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
enum NewsKind {
  SpecialNotice,
  News,
  Topic
}

impl ToString for NewsKind {
  fn to_string(&self) -> String {
    match *self {
      NewsKind::News => "News",
      NewsKind::Topic => "Topic",
      NewsKind::SpecialNotice => "Special notice"
    }.to_string()
  }
}

fn main() {
  dotenv::dotenv().ok();

  let webhook_url = match env::var("LB_NEWS_WEBHOOK") {
    Ok(u) => u,
    Err(_) => {
      println!("No webhook specified");
      return;
    }
  };

  let database = match NewsDatabase::load() {
    Some(db) => db,
    None => NewsDatabase::default()
  };
  let mut scraper = NewsScraper::new(database);

  let news = match scraper.download_news() {
    Some(n) => n,
    None => {
      println!("could not download news");
      return;
    }
  };

  let downloaded_news = scraper.parse_news(&news);
  for (id, item) in downloaded_news {
    if !scraper.database.items.contains_key(&id) {
      let mut embed = json!({
        "type": "rich",
        "timestamp": UTC.timestamp(item.time, 0).to_rfc3339(),
        // "url": item.url,
        "fields": [
          {
            "name": "Title",
            "value": item.title,
            "inline": false
          },
          {
            "name": "Link",
            "value": item.url,
            "inline": false
          },
          {
            "name": "Kind",
            "value": item.kind.to_string(),
            "inline": true
          }
        ]
      });
      if let Some(ref tag) = item.tag {
        embed["fields"].as_array_mut().unwrap().push(json!({
          "name": "Tag",
          "value": tag,
          "inline": true
        }));
      }
      let data = json!({
        "embeds": [embed]
      });
      println!("{:#?}", item);
      let res = scraper.client.post(&webhook_url)
        .header(hyper::header::ContentType::json())
        .body(&data.to_string())
        .send();
      let mut data = match res {
        Ok(r) => r,
        Err(e) => {
          println!("error sending webhook: {}", e);
          continue;
        }
      };
      let mut content = String::new();
      if let Err(e) = data.read_to_string(&mut content) {
        println!("could not read webhook response: {}", e);
        continue;
      }
      if data.status.class() != hyper::status::StatusClass::Success {
        println!("discord says no");
        println!("{}", content);
      } else {
        println!("webhook sent");
        scraper.database.items.insert(id, item);
      }
    }
  }
  if scraper.database.save().is_none() {
    println!("could not save database");
  }
}

impl NewsDatabase {
  fn load() -> Option<Self> {
    let news_file = match File::open("./lodestone_news.json") {
      Ok(n) => n,
      Err(_) => return None
    };
    serde_json::from_reader(news_file).ok()
  }

  fn save(&self) -> Option<()> {
    let news_file = match OpenOptions::new().create(true).write(true).open("./lodestone_news.json") {
      Ok(n) => n,
      Err(_) => return None
    };
    serde_json::to_writer(news_file, self).ok()
  }
}

impl NewsScraper {
  fn new(database: NewsDatabase) -> Self {
    NewsScraper {
      client: Client::with_connector(hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())),
      database: database
    }
  }

  fn download_news(&self) -> Option<String> {
    let mut response = match self.client.get(NEWS_URL).send() {
      Ok(r) => r,
      Err(_) => return None
    };

    let mut content = String::new();
    if response.read_to_string(&mut content).is_err() {
      return None;
    }
    Some(content)
  }

  fn parse_news(&self, news: &str) -> HashMap<String, NewsItem> {
    let html = Html::parse_document(news);
    let news_selector = Selector::parse("div.news__content.parts__space--add > ul:nth-of-type(2) > li").unwrap();
    let topics_selector = Selector::parse("div.news__content.parts__space--add > ul:nth-of-type(3) > li").unwrap();
    let title_selector = Selector::parse("p.news__list--title").unwrap();
    let time_script_selector = Selector::parse("time.news__list--time > script").unwrap();
    let mut lis: Vec<_> = html.select(&news_selector).map(|x| (NewsKind::News, x)).collect();
    lis.append(&mut html.select(&topics_selector).map(|x| (NewsKind::Topic, x)).collect());
    let mut items = HashMap::with_capacity(lis.len());
    for (kind, li) in lis {
      let child = match kind {
        NewsKind::News => li.first_child().and_then(|v| v.value().as_element()),
        NewsKind::Topic => li.select(&title_selector).next().and_then(|v| v.first_child().and_then(|x| x.value().as_element())),
        _ => {
          println!("unsupported news kind");
          continue;
        }
      };

      let child = match child {
        Some(c) => c,
        None => {
          println!("could not get news item child");
          continue
        }
      };

      let href = match child.attr("href") {
        Some(h) => h,
        None => {
          println!("invalid link in news item");
          continue;
        }
      };

      let (title, tag) = match kind {
        NewsKind::News => {
          let title = match li.select(&title_selector).next() {
            Some(t) => t,
            None => {
              println!("missing title in news item");
              continue;
            }
          };

          let tag: Option<String> = title.first_child()
            .and_then(scraper::ElementRef::wrap)
            .map(|c| c.text().collect::<String>())
            .map(|tag| tag[1..tag.len() - 1].to_string());

          let text_iter = title.text();
          let title: String = if tag.is_some() {
            text_iter.skip(1).collect()
          } else {
            text_iter.collect()
          };

          (title, tag)
        },
        NewsKind::Topic => {
          let text = li.select(&title_selector).next()
            .and_then(|v| v.first_child())
            .and_then(scraper::ElementRef::wrap)
            .map(|v| v.text().collect());
          match text {
            Some(t) => (t, None),
            None => {
              println!("invalid topic: no title");
              continue;
            }
          }
        },
        _ => unreachable!()
      };

      let time_script = match li.select(&time_script_selector).next() {
        Some(ts) => ts,
        None => {
          println!("news item missing time script");
          continue
        }
      };

      let time_script: String = time_script.text().collect();
      let time_string = match time_script.split("strftime(").nth(1).and_then(|v| v.split(',').next()) {
        Some(time) => time,
        None => {
          println!("invalid script in news item");
          continue
        }
      };
      let time: i64 = match time_string.parse() {
        Ok(t) => t,
        Err(_) => {
          println!("invalid time in time script");
          continue
        }
      };

      let id = match href.split('/').last() {
        Some(i) => i,
        None => {
          println!("invalid href in news item");
          continue;
        }
      };

      let news_item = NewsItem {
        title: title,
        url: format!("http://na.finalfantasyxiv.com{}", href),
        kind: kind,
        time: time,
        tag: tag
      };
      items.insert(id.to_string(), news_item);
    }

    items
  }
}
<commit_msg>chore: remove old comment<commit_after>extern crate hyper;
extern crate hyper_rustls;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate scraper;
extern crate chrono;
extern crate dotenv;

use hyper::Client;

use scraper::{Html, Selector};

use chrono::{UTC, TimeZone};

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::env;

const NEWS_URL: &'static str = "http://na.finalfantasyxiv.com/lodestone/news/";

struct NewsScraper {
  client: Client,
  database: NewsDatabase
}

#[derive(Debug, Default, Serialize, Deserialize)]
struct NewsDatabase {
  items: HashMap<String, NewsItem>
}

#[derive(Debug, Serialize, Deserialize)]
struct NewsItem {
  title: String,
  url: String,
  kind: NewsKind,
  time: i64,
  tag: Option<String>
}

#[derive(Debug, Serialize, Deserialize)]
enum NewsKind {
  SpecialNotice,
  News,
  Topic
}

impl ToString for NewsKind {
  fn to_string(&self) -> String {
    match *self {
      NewsKind::News => "News",
      NewsKind::Topic => "Topic",
      NewsKind::SpecialNotice => "Special notice"
    }.to_string()
  }
}

fn main() {
  dotenv::dotenv().ok();

  let webhook_url = match env::var("LB_NEWS_WEBHOOK") {
    Ok(u) => u,
    Err(_) => {
      println!("No webhook specified");
      return;
    }
  };

  let database = match NewsDatabase::load() {
    Some(db) => db,
    None => NewsDatabase::default()
  };
  let mut scraper = NewsScraper::new(database);

  let news = match scraper.download_news() {
    Some(n) => n,
    None => {
      println!("could not download news");
      return;
    }
  };

  let downloaded_news = scraper.parse_news(&news);
  for (id, item) in downloaded_news {
    if !scraper.database.items.contains_key(&id) {
      let mut embed = json!({
        "type": "rich",
        "timestamp": UTC.timestamp(item.time, 0).to_rfc3339(),
        "fields": [
          {
            "name": "Title",
            "value": item.title,
            "inline": false
          },
          {
            "name": "Link",
            "value": item.url,
            "inline": false
          },
          {
            "name": "Kind",
            "value": item.kind.to_string(),
            "inline": true
          }
        ]
      });
      if let Some(ref tag) = item.tag {
        embed["fields"].as_array_mut().unwrap().push(json!({
          "name": "Tag",
          "value": tag,
          "inline": true
        }));
      }
      let data = json!({
        "embeds": [embed]
      });
      println!("{:#?}", item);
      let res = scraper.client.post(&webhook_url)
        .header(hyper::header::ContentType::json())
        .body(&data.to_string())
        .send();
      let mut data = match res {
        Ok(r) => r,
        Err(e) => {
          println!("error sending webhook: {}", e);
          continue;
        }
      };
      let mut content = String::new();
      if let Err(e) = data.read_to_string(&mut content) {
        println!("could not read webhook response: {}", e);
        continue;
      }
      if data.status.class() != hyper::status::StatusClass::Success {
        println!("discord says no");
        println!("{}", content);
      } else {
        println!("webhook sent");
        scraper.database.items.insert(id, item);
      }
    }
  }
  if scraper.database.save().is_none() {
    println!("could not save database");
  }
}

impl NewsDatabase {
  fn load() -> Option<Self> {
    let news_file = match File::open("./lodestone_news.json") {
      Ok(n) => n,
      Err(_) => return None
    };
    serde_json::from_reader(news_file).ok()
  }

  fn save(&self) -> Option<()> {
    let news_file = match OpenOptions::new().create(true).write(true).open("./lodestone_news.json") {
      Ok(n) => n,
      Err(_) => return None
    };
    serde_json::to_writer(news_file, self).ok()
  }
}

impl NewsScraper {
  fn new(database: NewsDatabase) -> Self {
    NewsScraper {
      client: Client::with_connector(hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())),
      database: database
    }
  }

  fn download_news(&self) -> Option<String> {
    let mut response = match self.client.get(NEWS_URL).send() {
      Ok(r) => r,
      Err(_) => return None
    };

    let mut content = String::new();
    if response.read_to_string(&mut content).is_err() {
      return None;
    }
    Some(content)
  }

  fn parse_news(&self, news: &str) -> HashMap<String, NewsItem> {
    let html = Html::parse_document(news);
    let news_selector = Selector::parse("div.news__content.parts__space--add > ul:nth-of-type(2) > li").unwrap();
    let topics_selector = Selector::parse("div.news__content.parts__space--add > ul:nth-of-type(3) > li").unwrap();
    let title_selector = Selector::parse("p.news__list--title").unwrap();
    let time_script_selector = Selector::parse("time.news__list--time > script").unwrap();
    let mut lis: Vec<_> = html.select(&news_selector).map(|x| (NewsKind::News, x)).collect();
    lis.append(&mut html.select(&topics_selector).map(|x| (NewsKind::Topic, x)).collect());
    let mut items = HashMap::with_capacity(lis.len());
    for (kind, li) in lis {
      let child = match kind {
        NewsKind::News => li.first_child().and_then(|v| v.value().as_element()),
        NewsKind::Topic => li.select(&title_selector).next().and_then(|v| v.first_child().and_then(|x| x.value().as_element())),
        _ => {
          println!("unsupported news kind");
          continue;
        }
      };

      let child = match child {
        Some(c) => c,
        None => {
          println!("could not get news item child");
          continue
        }
      };

      let href = match child.attr("href") {
        Some(h) => h,
        None => {
          println!("invalid link in news item");
          continue;
        }
      };

      let (title, tag) = match kind {
        NewsKind::News => {
          let title = match li.select(&title_selector).next() {
            Some(t) => t,
            None => {
              println!("missing title in news item");
              continue;
            }
          };

          let tag: Option<String> = title.first_child()
            .and_then(scraper::ElementRef::wrap)
            .map(|c| c.text().collect::<String>())
            .map(|tag| tag[1..tag.len() - 1].to_string());

          let text_iter = title.text();
          let title: String = if tag.is_some() {
            text_iter.skip(1).collect()
          } else {
            text_iter.collect()
          };

          (title, tag)
        },
        NewsKind::Topic => {
          let text = li.select(&title_selector).next()
            .and_then(|v| v.first_child())
            .and_then(scraper::ElementRef::wrap)
            .map(|v| v.text().collect());
          match text {
            Some(t) => (t, None),
            None => {
              println!("invalid topic: no title");
              continue;
            }
          }
        },
        _ => unreachable!()
      };

      let time_script = match li.select(&time_script_selector).next() {
        Some(ts) => ts,
        None => {
          println!("news item missing time script");
          continue
        }
      };

      let time_script: String = time_script.text().collect();
      let time_string = match time_script.split("strftime(").nth(1).and_then(|v| v.split(',').next()) {
        Some(time) => time,
        None => {
          println!("invalid script in news item");
          continue
        }
      };
      let time: i64 = match time_string.parse() {
        Ok(t) => t,
        Err(_) => {
          println!("invalid time in time script");
          continue
        }
      };

      let id = match href.split('/').last() {
        Some(i) => i,
        None => {
          println!("invalid href in news item");
          continue;
        }
      };

      let news_item = NewsItem {
        title: title,
        url: format!("http://na.finalfantasyxiv.com{}", href),
        kind: kind,
        time: time,
        tag: tag
      };
      items.insert(id.to_string(), news_item);
    }

    items
  }
}
<|endoftext|>


<commit_before>use board::std_board;
use board::std_board::{ChessBoard, Piece, PieceType, Square};
use board::sjadam_move::{SjadamMove, SjadamUndoMove};
use board::sjadam_move_gen;
use board::std_move_gen::move_gen;

use search_algorithms::board::EvalBoard;
use search_algorithms::board::Color;
use search_algorithms::board::Color::*;
use search_algorithms::board::GameResult;

use rand;

use std::cell::RefCell;
use std::hash::{Hash, Hasher};

use uci::UciBoard;

#[derive(Debug)]
pub struct SjadamBoard {
    pub base_board: std_board::ChessBoard,
    legal_moves_cache: RefCell<Option<Vec<SjadamMove>>>,
}

impl Hash for SjadamBoard {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.base_board.hash(state);
    }
}

impl PartialEq for SjadamBoard {
    fn eq(&self, other: &SjadamBoard) -> bool {
        self.base_board == other.base_board
    }
}

impl Eq for SjadamBoard { }

impl Clone for SjadamBoard {
    fn clone(&self) -> Self {
        // When cloning the board, the cache is not brought along
        SjadamBoard { base_board: self.base_board.clone(), legal_moves_cache: RefCell::new(None) }
    }
}

impl EvalBoard for SjadamBoard {
    type Move = SjadamMove;
    type UndoMove = SjadamUndoMove;

    fn to_move(&self) -> Color {
        self.base_board.to_move()
    }

    fn do_move(&mut self, mv: Self::Move) -> Self::UndoMove {
        let start_color = self.to_move();
        *self.legal_moves_cache.borrow_mut() = None;
        debug_assert!(mv.from != mv.sjadam_square || mv.sjadam_square != mv.to);

        let undo_move = SjadamUndoMove {
            from: mv.from, sjadam_square: mv.sjadam_square,
            capture: self.base_board[mv.to].0,
            prom: mv.prom /*(self.base_board[mv.from].0 != PieceType::King)
                && (self.to_move() == Black && mv.to.0 >= 56
                    || self.to_move() == White && mv.to.0 < 8)*/,
            to: mv.to, piece_moved: self.base_board[mv.from].0,
            old_castling_en_passant: self.base_board.castling_en_passant,
            old_half_move_clock: self.base_board.half_move_clock };

        // Do sjadam move. Has no effect if there is no sjadam jump
        self.base_board[mv.sjadam_square] = self.base_board[mv.from];
        if mv.from != mv.sjadam_square {
            self.base_board[mv.from] = Piece::empty();
        }

        let chess_move = mv.chess_move(self);
        match chess_move {
            None => {
                if undo_move.piece_moved == PieceType::King {
                    let color = self.to_move();
                    self.base_board.disable_castling(color)
                }
                // TODO: Remove castling privileges on rook moves
                if undo_move.piece_moved != PieceType::Pawn {
                    self.base_board.half_move_clock += 1; // Pure sjadam moves are never captures
                }

                // Sjadam moves never create en passant squares
                self.base_board.set_en_passant_square(None);
                self.base_board.to_move = !self.base_board.to_move;

            }, // TODO: Figure out if anything else needs to be done here
            Some(chess_move) => {
                self.base_board.do_move(chess_move);
            },
        }

        for square in 56..63 {
            let Piece(piece, color) = self.base_board[Square(square)];
            if color == Black && piece != PieceType::King && piece != PieceType::Empty{
                self.base_board[Square(square)] = Piece(PieceType::Queen, Black)
            }
        }
        for square in 0..7 {
            let Piece(piece, color) = self.base_board[Square(square)];
            if color == White && piece != PieceType::King && piece != PieceType::Empty {
                self.base_board[Square(square)] = Piece(PieceType::Queen, White)
            }
        }
        debug_assert_ne!(start_color, self.to_move());
        undo_move
    }

    fn undo_move(&mut self, mv: Self::UndoMove) {
        let start_color = self.to_move();
        *self.legal_moves_cache.borrow_mut() = None;
        match mv.chess_move(self) {
            None => {
                self.base_board.to_move = !self.base_board.to_move;
            },
            Some(chess_move) => self.base_board.undo_move(chess_move),
        }
        self.base_board.half_move_clock = mv.old_half_move_clock;
        self.base_board.castling_en_passant = mv.old_castling_en_passant;

        let color = self.base_board[mv.sjadam_square].1;
        self.base_board[mv.from] = Piece(mv.piece_moved, color);
        // Undo sjadam move
        if mv.from != mv.sjadam_square {
            self.base_board[mv.sjadam_square] = Piece::empty();
        }
        debug_assert_ne!(start_color, self.to_move());

    }

    fn start_board() -> Self {
        SjadamBoard {
            base_board: std_board::ChessBoard::start_board(),
            legal_moves_cache: RefCell::new(None),
        }
    }

    fn all_legal_moves(&self) -> Vec<Self::Move> {

        /*let moves;
        let mut moves_cache = self.legal_moves_cache.borrow_mut();
        if (*moves_cache).is_some(){
            moves = (*moves_cache).as_ref().unwrap().clone();
        }
        else {*/
        let mut cloned_board = self.clone();
        let moves = sjadam_move_gen::all_legal_moves(&mut cloned_board);
        //*moves_cache = Some(moves.clone());

        moves
    }

    fn game_result(&self) -> Option<GameResult> {
        // In sjadam, king may be actually captured.
        // Check if the king is gone
        match self.king_on_board() {
            (true, false) => Some(GameResult::WhiteWin),
            (false, true) => Some(GameResult::BlackWin),
            (false, false) => panic!("Neither side has a king on the board:\n{:?}", self),
            (true, true) => {
                // First check if it is attacked by regular moves
                if !sjadam_move_gen::any_legal_moves(&mut self.clone()) {
                    if move_gen::is_attacked(&self.base_board, move_gen::king_pos(&self.base_board)) {
                        match self.to_move() {
                            Black => Some(GameResult::WhiteWin),
                            White => Some(GameResult::BlackWin),
                        }
                    }
                    else {
                        Some(GameResult::Draw)
                    }
                }
                // The king might still be directly captured by a sjadam move next move,
                // but that is fine
                else {
                    if self.base_board.half_move_clock > 50 {
                        Some(GameResult::Draw)
                    }
                    else { None }
                }
            },
        }
    }
    fn do_random_move<R: rand::Rng>(&mut self, rng: &mut R) {
        let moves = self.all_legal_moves();
        assert!(!moves.is_empty(),
                "Tried to do a random move, but there were no legal moves on {:?}",
                self);
        self.do_move(moves[rng.gen_range(0, moves.len())].clone());
    }
    fn eval_board(&self) -> f32 {
        self.base_board.eval_board()
    }
}

impl UciBoard for SjadamBoard {
    fn from_fen(input: &str) -> Result<Self, String> {
        Ok(SjadamBoard { base_board: try!(ChessBoard::from_fen(input)),
                         legal_moves_cache: RefCell::new(None), })
    }

    fn to_fen(&self) -> String {
        self.base_board.to_fen()
    }
}

impl SjadamBoard {
    fn king_on_board(&self) -> (bool, bool) {
        let mut white_king = false;
        let mut black_king = false;
        let board = &self.base_board;
        for square in std_board::BoardIter::new() {
            match board[square] {
                Piece(PieceType::King, White) => white_king = true,
                Piece(PieceType::King, Black) => black_king = true,
                _ => (),
            }
        }
        (white_king, black_king)
    }
}
<commit_msg>Removed move cache from sjadam board<commit_after>use board::std_board;
use board::std_board::{ChessBoard, Piece, PieceType, Square};
use board::sjadam_move::{SjadamMove, SjadamUndoMove};
use board::sjadam_move_gen;
use board::std_move_gen::move_gen;

use search_algorithms::board::EvalBoard;
use search_algorithms::board::Color;
use search_algorithms::board::Color::*;
use search_algorithms::board::GameResult;

use rand;
use uci::UciBoard;

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct SjadamBoard {
    pub base_board: std_board::ChessBoard,
}

impl EvalBoard for SjadamBoard {
    type Move = SjadamMove;
    type UndoMove = SjadamUndoMove;

    fn to_move(&self) -> Color {
        self.base_board.to_move()
    }

    fn do_move(&mut self, mv: Self::Move) -> Self::UndoMove {
        let start_color = self.to_move();
        debug_assert!(mv.from != mv.sjadam_square || mv.sjadam_square != mv.to);

        let undo_move = SjadamUndoMove {
            from: mv.from, sjadam_square: mv.sjadam_square,
            capture: self.base_board[mv.to].0,
            prom: mv.prom,
            to: mv.to, piece_moved: self.base_board[mv.from].0,
            old_castling_en_passant: self.base_board.castling_en_passant,
            old_half_move_clock: self.base_board.half_move_clock };

        // Do sjadam move. Has no effect if there is no sjadam jump
        self.base_board[mv.sjadam_square] = self.base_board[mv.from];
        if mv.from != mv.sjadam_square {
            self.base_board[mv.from] = Piece::empty();
        }

        let chess_move = mv.chess_move(self);
        match chess_move {
            None => {
                if undo_move.piece_moved == PieceType::King {
                    let color = self.to_move();
                    self.base_board.disable_castling(color)
                }
                // TODO: Remove castling privileges on rook moves
                if undo_move.piece_moved != PieceType::Pawn {
                    self.base_board.half_move_clock += 1; // Pure sjadam moves are never captures
                }

                // Sjadam moves never create en passant squares
                self.base_board.set_en_passant_square(None);
                self.base_board.to_move = !self.base_board.to_move;

            }, // TODO: Figure out if anything else needs to be done here
            Some(chess_move) => {
                self.base_board.do_move(chess_move);
            },
        }

        for square in 56..63 {
            let Piece(piece, color) = self.base_board[Square(square)];
            if color == Black && piece != PieceType::King && piece != PieceType::Empty{
                self.base_board[Square(square)] = Piece(PieceType::Queen, Black)
            }
        }
        for square in 0..7 {
            let Piece(piece, color) = self.base_board[Square(square)];
            if color == White && piece != PieceType::King && piece != PieceType::Empty {
                self.base_board[Square(square)] = Piece(PieceType::Queen, White)
            }
        }
        debug_assert_ne!(start_color, self.to_move());
        undo_move
    }

    fn undo_move(&mut self, mv: Self::UndoMove) {
        let start_color = self.to_move();
        match mv.chess_move(self) {
            None => {
                self.base_board.to_move = !self.base_board.to_move;
            },
            Some(chess_move) => self.base_board.undo_move(chess_move),
        }
        self.base_board.half_move_clock = mv.old_half_move_clock;
        self.base_board.castling_en_passant = mv.old_castling_en_passant;

        let color = self.base_board[mv.sjadam_square].1;
        self.base_board[mv.from] = Piece(mv.piece_moved, color);
        // Undo sjadam move
        if mv.from != mv.sjadam_square {
            self.base_board[mv.sjadam_square] = Piece::empty();
        }
        debug_assert_ne!(start_color, self.to_move());

    }

    fn start_board() -> Self {
        SjadamBoard {
            base_board: std_board::ChessBoard::start_board(),
        }
    }

    fn all_legal_moves(&self) -> Vec<Self::Move> {
        let mut cloned_board = self.clone();
        sjadam_move_gen::all_legal_moves(&mut cloned_board)
    }

    fn game_result(&self) -> Option<GameResult> {
        // In sjadam, king may be actually captured.
        // Check if the king is gone
        match self.king_on_board() {
            (true, false) => Some(GameResult::WhiteWin),
            (false, true) => Some(GameResult::BlackWin),
            (false, false) => panic!("Neither side has a king on the board:\n{:?}", self),
            (true, true) => {
                // First check if it is attacked by regular moves
                if !sjadam_move_gen::any_legal_moves(&mut self.clone()) {
                    if move_gen::is_attacked(&self.base_board, move_gen::king_pos(&self.base_board)) {
                        match self.to_move() {
                            Black => Some(GameResult::WhiteWin),
                            White => Some(GameResult::BlackWin),
                        }
                    }
                    else {
                        Some(GameResult::Draw)
                    }
                }
                // The king might still be directly captured by a sjadam move next move,
                // but that is fine
                else {
                    if self.base_board.half_move_clock > 50 {
                        Some(GameResult::Draw)
                    }
                    else { None }
                }
            },
        }
    }
    fn do_random_move<R: rand::Rng>(&mut self, rng: &mut R) {
        let moves = self.all_legal_moves();
        assert!(!moves.is_empty(),
                "Tried to do a random move, but there were no legal moves on {:?}",
                self);
        self.do_move(moves[rng.gen_range(0, moves.len())].clone());
    }
    fn eval_board(&self) -> f32 {
        self.base_board.eval_board()
    }
}

impl UciBoard for SjadamBoard {
    fn from_fen(input: &str) -> Result<Self, String> {
        Ok(SjadamBoard { base_board: try!(ChessBoard::from_fen(input)) })
    }

    fn to_fen(&self) -> String {
        self.base_board.to_fen()
    }
}

impl SjadamBoard {
    fn king_on_board(&self) -> (bool, bool) {
        let mut white_king = false;
        let mut black_king = false;
        let board = &self.base_board;
        for square in std_board::BoardIter::new() {
            match board[square] {
                Piece(PieceType::King, White) => white_king = true,
                Piece(PieceType::King, Black) => black_king = true,
                _ => (),
            }
        }
        (white_king, black_king)
    }
}
<|endoftext|>


<commit_before>//! Functions to aid the `Api::to_tokens` method.

use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::collections::BTreeSet;
use syn::{
    AngleBracketedGenericArguments, GenericArgument, Ident, Lifetime,
    ParenthesizedGenericArguments, PathArguments, Type, TypePath, TypeReference,
};

use crate::api::{metadata::Metadata, request::Request};

pub fn collect_lifetime_ident(lifetimes: &mut BTreeSet<Lifetime>, ty: &Type) {
    match ty {
        Type::Path(TypePath { path, .. }) => {
            for seg in &path.segments {
                match &seg.arguments {
                    PathArguments::AngleBracketed(AngleBracketedGenericArguments {
                        args, ..
                    }) => {
                        for gen in args {
                            if let GenericArgument::Type(ty) = gen {
                                collect_lifetime_ident(lifetimes, &ty)
                            } else if let GenericArgument::Lifetime(lt) = gen {
                                lifetimes.insert(lt.clone());
                            }
                        }
                    }
                    PathArguments::Parenthesized(ParenthesizedGenericArguments {
                        inputs, ..
                    }) => {
                        for ty in inputs {
                            collect_lifetime_ident(lifetimes, ty)
                        }
                    }
                    _ => {}
                }
            }
        }
        Type::Reference(TypeReference { elem, lifetime, .. }) => {
            collect_lifetime_ident(lifetimes, &*elem);
            if let Some(lt) = lifetime {
                lifetimes.insert(lt.clone());
            }
        }
        Type::Tuple(syn::TypeTuple { elems, .. }) => {
            for ty in elems {
                collect_lifetime_ident(lifetimes, ty)
            }
        }
        _ => {}
    }
}

/// Generates a `TokenStream` of lifetime identifiers `<'lifetime>`.
pub fn generics_to_tokens<'a, I: Iterator<Item = &'a Lifetime>>(lifetimes: I) -> TokenStream {
    let lifetimes = lifetimes.collect::<Vec<_>>();
    if lifetimes.is_empty() {
        TokenStream::new()
    } else {
        let lifetimes = quote! { #( #lifetimes ),* };
        quote! { < #lifetimes > }
    }
}

/// The first item in the tuple generates code for the request path from
/// the `Metadata` and `Request` structs. The second item in the returned tuple
/// is the code to generate a Request struct field created from any segments
/// of the path that start with ":".
///
/// The first `TokenStream` returned is the constructed url path. The second `TokenStream` is
/// used for implementing `TryFrom<http::Request<Vec<u8>>>`, from path strings deserialized to Ruma
/// types.
pub(crate) fn request_path_string_and_parse(
    request: &Request,
    metadata: &Metadata,
) -> (TokenStream, TokenStream) {
    if request.has_path_fields() {
        let path_string = metadata.path.value();

        assert!(path_string.starts_with('/'), "path needs to start with '/'");
        assert!(
            path_string.chars().filter(|c| *c == ':').count() == request.path_field_count(),
            "number of declared path parameters needs to match amount of placeholders in path"
        );

        let format_call = {
            let mut format_string = path_string.clone();
            let mut format_args = Vec::new();

            while let Some(start_of_segment) = format_string.find(':') {
                // ':' should only ever appear at the start of a segment
                assert_eq!(&format_string[start_of_segment - 1..start_of_segment], "/");

                let end_of_segment = match format_string[start_of_segment..].find('/') {
                    Some(rel_pos) => start_of_segment + rel_pos,
                    None => format_string.len(),
                };

                let path_var = Ident::new(
                    &format_string[start_of_segment + 1..end_of_segment],
                    Span::call_site(),
                );
                format_args.push(quote! {
                    ::ruma_api::exports::percent_encoding::utf8_percent_encode(
                        &self.#path_var.to_string(),
                        ::ruma_api::exports::percent_encoding::NON_ALPHANUMERIC,
                    )
                });
                format_string.replace_range(start_of_segment..end_of_segment, "{}");
            }

            quote! {
                format_args!(#format_string, #(#format_args),*)
            }
        };

        let path_fields =
            path_string[1..].split('/').enumerate().filter(|(_, s)| s.starts_with(':')).map(
                |(i, segment)| {
                    let path_var = &segment[1..];
                    let path_var_ident = Ident::new(path_var, Span::call_site());
                    quote! {
                        #path_var_ident: {
                            use ::ruma_api::error::RequestDeserializationError;

                            let segment = path_segments.get(#i).unwrap().as_bytes();
                            let decoded = ::ruma_api::try_deserialize!(
                                request,
                                ::ruma_api::exports::percent_encoding::percent_decode(segment)
                                    .decode_utf8(),
                            );

                            ::ruma_api::try_deserialize!(request, ::std::convert::TryFrom::try_from(&*decoded))
                        }
                    }
                },
            );

        (format_call, quote! { #(#path_fields,)* })
    } else {
        (quote! { metadata.path.to_owned() }, TokenStream::new())
    }
}

/// The function determines the type of query string that needs to be built
/// and then builds it using `ruma_serde::urlencoded::to_string`.
pub(crate) fn build_query_string(request: &Request) -> TokenStream {
    if let Some(field) = request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have identifier");

        quote!({
            // This function exists so that the compiler will throw an
            // error when the type of the field with the query_map
            // attribute doesn't implement IntoIterator<Item = (String, String)>
            //
            // This is necessary because the ruma_serde::urlencoded::to_string
            // call will result in a runtime error when the type cannot be
            // encoded as a list key-value pairs (?key1=value1&key2=value2)
            //
            // By asserting that it implements the iterator trait, we can
            // ensure that it won't fail.
            fn assert_trait_impl<T>(_: &T)
            where
                T: ::std::iter::IntoIterator<Item = (::std::string::String, ::std::string::String)>,
            {}

            let request_query = RequestQuery(self.#field_name);
            assert_trait_impl(&request_query.0);

            format_args!(
                "?{}",
                ::ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?
            )
        })
    } else if request.has_query_fields() {
        let request_query_init_fields = request.request_query_init_fields();

        quote!({
            let request_query = RequestQuery {
                #request_query_init_fields
            };

            format_args!(
                "?{}",
                ::ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?
            )
        })
    } else {
        quote! { "" }
    }
}

/// Deserialize the query string.
pub(crate) fn extract_request_query(request: &Request) -> TokenStream {
    if request.query_map_field().is_some() {
        quote! {
            let request_query = ::ruma_api::try_deserialize!(
                request,
                ::ruma_api::exports::ruma_serde::urlencoded::from_str(
                    &request.uri().query().unwrap_or("")
                ),
            );
        }
    } else if request.has_query_fields() {
        let request_query_type = if request.has_query_lifetimes() {
            quote! { IncomingRequestQuery }
        } else {
            quote! { RequestQuery }
        };
        quote! {
            let request_query: #request_query_type = ::ruma_api::try_deserialize!(
                request,
                ::ruma_api::exports::ruma_serde::urlencoded::from_str(
                    &request.uri().query().unwrap_or("")
                ),
            );
        }
    } else {
        TokenStream::new()
    }
}

/// Generates the code to initialize a `Request`.
///
/// Used to construct an `http::Request`s body.
pub(crate) fn build_request_body(request: &Request) -> TokenStream {
    if let Some(field) = request.newtype_raw_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote!(self.#field_name)
    } else if request.has_body_fields() || request.newtype_body_field().is_some() {
        let request_body_initializers = if let Some(field) = request.newtype_body_field() {
            let field_name = field.ident.as_ref().expect("expected field to have an identifier");
            quote! { (self.#field_name) }
        } else {
            let initializers = request.request_body_init_fields();
            quote! { { #initializers } }
        };

        quote! {
            {
                let request_body = RequestBody #request_body_initializers;
                ::ruma_api::exports::serde_json::to_vec(&request_body)?
            }
        }
    } else {
        quote!(Vec::new())
    }
}

pub(crate) fn parse_request_body(request: &Request) -> TokenStream {
    if let Some(field) = request.newtype_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote! {
            #field_name: request_body.0,
        }
    } else if let Some(field) = request.newtype_raw_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote! {
            #field_name: request.into_body(),
        }
    } else {
        request.request_init_body_fields()
    }
}

pub(crate) fn req_res_meta_word<T>(
    attr_kind: &str,
    field: &syn::Field,
    newtype_body_field: &mut Option<syn::Field>,
    body_field_kind: T,
    raw_field_kind: T,
) -> syn::Result<T> {
    if let Some(f) = &newtype_body_field {
        let mut error = syn::Error::new_spanned(field, "There can only be one newtype body field");
        error.combine(syn::Error::new_spanned(f, "Previous newtype body field"));
        return Err(error);
    }

    *newtype_body_field = Some(field.clone());
    Ok(match attr_kind {
        "body" => body_field_kind,
        "raw_body" => raw_field_kind,
        _ => unreachable!(),
    })
}

pub(crate) fn req_res_name_value<T>(
    name: Ident,
    value: Ident,
    header: &mut Option<Ident>,
    field_kind: T,
) -> syn::Result<T> {
    if name != "header" {
        return Err(syn::Error::new_spanned(
            name,
            "Invalid #[ruma_api] argument with value, expected `header`",
        ));
    }

    *header = Some(value);
    Ok(field_kind)
}

pub(crate) fn is_valid_endpoint_path(string: &str) -> bool {
    string.as_bytes().iter().all(|b| (0x21..=0x7E).contains(b))
}
<commit_msg>Add semicolons and the other possible syn::Type types<commit_after>//! Functions to aid the `Api::to_tokens` method.

use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::collections::BTreeSet;
use syn::{
    AngleBracketedGenericArguments, GenericArgument, Ident, Lifetime,
    ParenthesizedGenericArguments, PathArguments, Type, TypeGroup, TypeParen, TypePath,
    TypeReference, TypeTuple,
};

use crate::api::{metadata::Metadata, request::Request};

pub fn collect_lifetime_ident(lifetimes: &mut BTreeSet<Lifetime>, ty: &Type) {
    match ty {
        Type::Path(TypePath { path, .. }) => {
            for seg in &path.segments {
                match &seg.arguments {
                    PathArguments::AngleBracketed(AngleBracketedGenericArguments {
                        args, ..
                    }) => {
                        for gen in args {
                            if let GenericArgument::Type(ty) = gen {
                                collect_lifetime_ident(lifetimes, &ty);
                            } else if let GenericArgument::Lifetime(lt) = gen {
                                lifetimes.insert(lt.clone());
                            }
                        }
                    }
                    PathArguments::Parenthesized(ParenthesizedGenericArguments {
                        inputs, ..
                    }) => {
                        for ty in inputs {
                            collect_lifetime_ident(lifetimes, ty);
                        }
                    }
                    _ => {}
                }
            }
        }
        Type::Reference(TypeReference { elem, lifetime, .. }) => {
            collect_lifetime_ident(lifetimes, &*elem);
            if let Some(lt) = lifetime {
                lifetimes.insert(lt.clone());
            }
        }
        Type::Tuple(TypeTuple { elems, .. }) => {
            for ty in elems {
                collect_lifetime_ident(lifetimes, ty);
            }
        }
        Type::Paren(TypeParen { elem, .. }) => collect_lifetime_ident(lifetimes, &*elem),
        Type::Group(TypeGroup { elem, .. }) => collect_lifetime_ident(lifetimes, &*elem),
        _ => {}
    }
}

/// Generates a `TokenStream` of lifetime identifiers `<'lifetime>`.
pub fn generics_to_tokens<'a, I: Iterator<Item = &'a Lifetime>>(lifetimes: I) -> TokenStream {
    let lifetimes = lifetimes.collect::<Vec<_>>();
    if lifetimes.is_empty() {
        TokenStream::new()
    } else {
        let lifetimes = quote! { #( #lifetimes ),* };
        quote! { < #lifetimes > }
    }
}

/// The first item in the tuple generates code for the request path from
/// the `Metadata` and `Request` structs. The second item in the returned tuple
/// is the code to generate a Request struct field created from any segments
/// of the path that start with ":".
///
/// The first `TokenStream` returned is the constructed url path. The second `TokenStream` is
/// used for implementing `TryFrom<http::Request<Vec<u8>>>`, from path strings deserialized to Ruma
/// types.
pub(crate) fn request_path_string_and_parse(
    request: &Request,
    metadata: &Metadata,
) -> (TokenStream, TokenStream) {
    if request.has_path_fields() {
        let path_string = metadata.path.value();

        assert!(path_string.starts_with('/'), "path needs to start with '/'");
        assert!(
            path_string.chars().filter(|c| *c == ':').count() == request.path_field_count(),
            "number of declared path parameters needs to match amount of placeholders in path"
        );

        let format_call = {
            let mut format_string = path_string.clone();
            let mut format_args = Vec::new();

            while let Some(start_of_segment) = format_string.find(':') {
                // ':' should only ever appear at the start of a segment
                assert_eq!(&format_string[start_of_segment - 1..start_of_segment], "/");

                let end_of_segment = match format_string[start_of_segment..].find('/') {
                    Some(rel_pos) => start_of_segment + rel_pos,
                    None => format_string.len(),
                };

                let path_var = Ident::new(
                    &format_string[start_of_segment + 1..end_of_segment],
                    Span::call_site(),
                );
                format_args.push(quote! {
                    ::ruma_api::exports::percent_encoding::utf8_percent_encode(
                        &self.#path_var.to_string(),
                        ::ruma_api::exports::percent_encoding::NON_ALPHANUMERIC,
                    )
                });
                format_string.replace_range(start_of_segment..end_of_segment, "{}");
            }

            quote! {
                format_args!(#format_string, #(#format_args),*)
            }
        };

        let path_fields =
            path_string[1..].split('/').enumerate().filter(|(_, s)| s.starts_with(':')).map(
                |(i, segment)| {
                    let path_var = &segment[1..];
                    let path_var_ident = Ident::new(path_var, Span::call_site());
                    quote! {
                        #path_var_ident: {
                            use ::ruma_api::error::RequestDeserializationError;

                            let segment = path_segments.get(#i).unwrap().as_bytes();
                            let decoded = ::ruma_api::try_deserialize!(
                                request,
                                ::ruma_api::exports::percent_encoding::percent_decode(segment)
                                    .decode_utf8(),
                            );

                            ::ruma_api::try_deserialize!(request, ::std::convert::TryFrom::try_from(&*decoded))
                        }
                    }
                },
            );

        (format_call, quote! { #(#path_fields,)* })
    } else {
        (quote! { metadata.path.to_owned() }, TokenStream::new())
    }
}

/// The function determines the type of query string that needs to be built
/// and then builds it using `ruma_serde::urlencoded::to_string`.
pub(crate) fn build_query_string(request: &Request) -> TokenStream {
    if let Some(field) = request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have identifier");

        quote!({
            // This function exists so that the compiler will throw an
            // error when the type of the field with the query_map
            // attribute doesn't implement IntoIterator<Item = (String, String)>
            //
            // This is necessary because the ruma_serde::urlencoded::to_string
            // call will result in a runtime error when the type cannot be
            // encoded as a list key-value pairs (?key1=value1&key2=value2)
            //
            // By asserting that it implements the iterator trait, we can
            // ensure that it won't fail.
            fn assert_trait_impl<T>(_: &T)
            where
                T: ::std::iter::IntoIterator<Item = (::std::string::String, ::std::string::String)>,
            {}

            let request_query = RequestQuery(self.#field_name);
            assert_trait_impl(&request_query.0);

            format_args!(
                "?{}",
                ::ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?
            )
        })
    } else if request.has_query_fields() {
        let request_query_init_fields = request.request_query_init_fields();

        quote!({
            let request_query = RequestQuery {
                #request_query_init_fields
            };

            format_args!(
                "?{}",
                ::ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?
            )
        })
    } else {
        quote! { "" }
    }
}

/// Deserialize the query string.
pub(crate) fn extract_request_query(request: &Request) -> TokenStream {
    if request.query_map_field().is_some() {
        quote! {
            let request_query = ::ruma_api::try_deserialize!(
                request,
                ::ruma_api::exports::ruma_serde::urlencoded::from_str(
                    &request.uri().query().unwrap_or("")
                ),
            );
        }
    } else if request.has_query_fields() {
        let request_query_type = if request.has_query_lifetimes() {
            quote! { IncomingRequestQuery }
        } else {
            quote! { RequestQuery }
        };
        quote! {
            let request_query: #request_query_type = ::ruma_api::try_deserialize!(
                request,
                ::ruma_api::exports::ruma_serde::urlencoded::from_str(
                    &request.uri().query().unwrap_or("")
                ),
            );
        }
    } else {
        TokenStream::new()
    }
}

/// Generates the code to initialize a `Request`.
///
/// Used to construct an `http::Request`s body.
pub(crate) fn build_request_body(request: &Request) -> TokenStream {
    if let Some(field) = request.newtype_raw_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote!(self.#field_name)
    } else if request.has_body_fields() || request.newtype_body_field().is_some() {
        let request_body_initializers = if let Some(field) = request.newtype_body_field() {
            let field_name = field.ident.as_ref().expect("expected field to have an identifier");
            quote! { (self.#field_name) }
        } else {
            let initializers = request.request_body_init_fields();
            quote! { { #initializers } }
        };

        quote! {
            {
                let request_body = RequestBody #request_body_initializers;
                ::ruma_api::exports::serde_json::to_vec(&request_body)?
            }
        }
    } else {
        quote!(Vec::new())
    }
}

pub(crate) fn parse_request_body(request: &Request) -> TokenStream {
    if let Some(field) = request.newtype_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote! {
            #field_name: request_body.0,
        }
    } else if let Some(field) = request.newtype_raw_body_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");
        quote! {
            #field_name: request.into_body(),
        }
    } else {
        request.request_init_body_fields()
    }
}

pub(crate) fn req_res_meta_word<T>(
    attr_kind: &str,
    field: &syn::Field,
    newtype_body_field: &mut Option<syn::Field>,
    body_field_kind: T,
    raw_field_kind: T,
) -> syn::Result<T> {
    if let Some(f) = &newtype_body_field {
        let mut error = syn::Error::new_spanned(field, "There can only be one newtype body field");
        error.combine(syn::Error::new_spanned(f, "Previous newtype body field"));
        return Err(error);
    }

    *newtype_body_field = Some(field.clone());
    Ok(match attr_kind {
        "body" => body_field_kind,
        "raw_body" => raw_field_kind,
        _ => unreachable!(),
    })
}

pub(crate) fn req_res_name_value<T>(
    name: Ident,
    value: Ident,
    header: &mut Option<Ident>,
    field_kind: T,
) -> syn::Result<T> {
    if name != "header" {
        return Err(syn::Error::new_spanned(
            name,
            "Invalid #[ruma_api] argument with value, expected `header`",
        ));
    }

    *header = Some(value);
    Ok(field_kind)
}

pub(crate) fn is_valid_endpoint_path(string: &str) -> bool {
    string.as_bytes().iter().all(|b| (0x21..=0x7E).contains(b))
}
<|endoftext|>


<commit_before>use std::time::{Duration, UNIX_EPOCH};

use matches::assert_matches;
use ruma_common::Raw;
use ruma_events::{
    custom::CustomEventContent, AnyMessageEvent, AnyStateEvent, AnyStateEventContent,
    AnySyncMessageEvent, AnySyncRoomEvent, MessageEvent, StateEvent, SyncMessageEvent,
    SyncStateEvent, Unsigned,
};
use ruma_identifiers::{event_id, room_id, user_id};
use serde_json::{
    from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};

fn custom_state_event() -> JsonValue {
    json!({
        "content": {
            "m.relates_to": {
                "event_id": "$MDitXXXXXX",
                "key": "👍",
                "rel_type": "m.annotation"
            }
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!room:room.com",
        "sender": "@carl:example.com",
        "state_key": "",
        "type": "m.reaction",
        "unsigned": {
            "age": 85
        }
    })
}

#[test]
fn serialize_custom_message_event() {
    let aliases_event = AnyMessageEvent::Custom(MessageEvent {
        content: CustomEventContent {
            json: json!({
                "body": " * edited message",
                "m.new_content": {
                    "body": "edited message",
                    "msgtype": "m.text"
                },
                "m.relates_to": {
                    "event_id": "some event id",
                    "rel_type": "m.replace"
                },
                "msgtype": "m.text"
            }),
            event_type: "m.room.message".into(),
        },
        event_id: event_id!("$h29iv0s8:example.com"),
        origin_server_ts: UNIX_EPOCH + Duration::from_millis(10),
        room_id: room_id!("!room:room.com"),
        sender: user_id!("@carl:example.com"),
        unsigned: Unsigned::default(),
    });

    let actual = to_json_value(&aliases_event).unwrap();
    let expected = json!({
        "content": {
            "body": " * edited message",
            "m.new_content": {
                "body": "edited message",
                "msgtype": "m.text"
            },
            "m.relates_to": {
                "event_id": "some event id",
                "rel_type": "m.replace"
            },
            "msgtype": "m.text"
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "sender": "@carl:example.com",
        "room_id": "!room:room.com",
        "type": "m.room.message",
    });

    assert_eq!(actual, expected);
}

#[test]
fn serialize_custom_state_event() {
    let aliases_event = AnyStateEvent::Custom(StateEvent {
        content: CustomEventContent {
            json: json!({
                "custom": 10
            }),
            event_type: "m.made.up".into(),
        },
        event_id: event_id!("$h29iv0s8:example.com"),
        origin_server_ts: UNIX_EPOCH + Duration::from_millis(10),
        prev_content: None,
        room_id: room_id!("!roomid:room.com"),
        sender: user_id!("@carl:example.com"),
        state_key: "".into(),
        unsigned: Unsigned::default(),
    });

    let actual = to_json_value(&aliases_event).unwrap();
    let expected = json!({
        "content": {
            "custom": 10
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!roomid:room.com",
        "sender": "@carl:example.com",
        "state_key": "",
        "type": "m.made.up",
    });

    assert_eq!(actual, expected);
}

#[test]
fn deserialize_custom_state_event() {
    let json_data = custom_state_event();

    let expected_content = json!({
        "m.relates_to": {
            "event_id": "$MDitXXXXXX",
            "key": "👍",
            "rel_type": "m.annotation"
        }
    });

    assert_matches!(
        from_json_value::<Raw<AnyStateEvent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap(),
        AnyStateEvent::Custom(StateEvent {
            content: CustomEventContent {
                json, event_type,
            },
            event_id,
            origin_server_ts,
            sender,
            room_id,
            prev_content: None,
            state_key,
            unsigned,
        }) if json == expected_content && event_type == "m.reaction"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && room_id == room_id!("!room:room.com")
            && state_key == ""
            && !unsigned.is_empty()
    );
}

#[test]
fn deserialize_custom_state_sync_event() {
    let json_data = custom_state_event();

    let expected_content = json!({
        "m.relates_to": {
            "event_id": "$MDitXXXXXX",
            "key": "👍",
            "rel_type": "m.annotation"
        }
    });

    assert_matches!(
        from_json_value::<SyncStateEvent<AnyStateEventContent>>(json_data)
            .unwrap(),
        SyncStateEvent {
            content: AnyStateEventContent::Custom(CustomEventContent {
                json, event_type,
            }),
            event_id,
            origin_server_ts,
            sender,
            prev_content: None,
            state_key,
            unsigned,
        } if json == expected_content && event_type == "m.reaction"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && state_key == ""
            && !unsigned.is_empty()
    );
}

#[test]
fn deserialize_custom_message_sync_event() {
    let json_data = json!({
        "content": {
            "m.relates_to": {
                "event_id": "$MDitXXXXXX",
                "key": "👍",
                "rel_type": "m.annotation"
            }
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!room:room.com",
        "sender": "@carl:example.com",
        "type": "m.reaction",
        "unsigned": {
            "age": 85
        }
    });

    let expected_content = json!({
        "m.relates_to": {
            "event_id": "$MDitXXXXXX",
            "key": "👍",
            "rel_type": "m.annotation"
        }
    });

    assert_matches!(
        from_json_value::<AnySyncRoomEvent>(json_data)
            .unwrap(),
        AnySyncRoomEvent::Message(AnySyncMessageEvent::Custom(SyncMessageEvent {
            content: CustomEventContent {
                json, event_type,
            },
            event_id,
            origin_server_ts,
            sender,
            unsigned,
        })) if json == expected_content && event_type == "m.reaction"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && !unsigned.is_empty()
    );
}
<commit_msg>events: Use a custom event for the custom event tests.<commit_after>use std::time::{Duration, UNIX_EPOCH};

use matches::assert_matches;
use ruma_common::Raw;
use ruma_events::{
    custom::CustomEventContent, AnyMessageEvent, AnyStateEvent, AnyStateEventContent,
    AnySyncMessageEvent, AnySyncRoomEvent, MessageEvent, StateEvent, SyncMessageEvent,
    SyncStateEvent, Unsigned,
};
use ruma_identifiers::{event_id, room_id, user_id};
use serde_json::{
    from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};

fn custom_state_event() -> JsonValue {
    json!({
        "content": {
            "m.relates_to": {
                "event_id": "$MDitXXXXXX",
                "key": "👍",
                "rel_type": "m.annotation"
            }
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!room:room.com",
        "sender": "@carl:example.com",
        "state_key": "",
        "type": "m.reaction",
        "unsigned": {
            "age": 85
        }
    })
}

#[test]
fn serialize_custom_message_event() {
    let aliases_event = AnyMessageEvent::Custom(MessageEvent {
        content: CustomEventContent {
            json: json!({
                "body": " * edited message",
                "m.new_content": {
                    "body": "edited message",
                    "msgtype": "m.text"
                },
                "m.relates_to": {
                    "event_id": "some event id",
                    "rel_type": "m.replace"
                },
                "msgtype": "m.text"
            }),
            event_type: "m.room.message".into(),
        },
        event_id: event_id!("$h29iv0s8:example.com"),
        origin_server_ts: UNIX_EPOCH + Duration::from_millis(10),
        room_id: room_id!("!room:room.com"),
        sender: user_id!("@carl:example.com"),
        unsigned: Unsigned::default(),
    });

    let actual = to_json_value(&aliases_event).unwrap();
    let expected = json!({
        "content": {
            "body": " * edited message",
            "m.new_content": {
                "body": "edited message",
                "msgtype": "m.text"
            },
            "m.relates_to": {
                "event_id": "some event id",
                "rel_type": "m.replace"
            },
            "msgtype": "m.text"
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "sender": "@carl:example.com",
        "room_id": "!room:room.com",
        "type": "m.room.message",
    });

    assert_eq!(actual, expected);
}

#[test]
fn serialize_custom_state_event() {
    let aliases_event = AnyStateEvent::Custom(StateEvent {
        content: CustomEventContent {
            json: json!({
                "custom": 10
            }),
            event_type: "m.made.up".into(),
        },
        event_id: event_id!("$h29iv0s8:example.com"),
        origin_server_ts: UNIX_EPOCH + Duration::from_millis(10),
        prev_content: None,
        room_id: room_id!("!roomid:room.com"),
        sender: user_id!("@carl:example.com"),
        state_key: "".into(),
        unsigned: Unsigned::default(),
    });

    let actual = to_json_value(&aliases_event).unwrap();
    let expected = json!({
        "content": {
            "custom": 10
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!roomid:room.com",
        "sender": "@carl:example.com",
        "state_key": "",
        "type": "m.made.up",
    });

    assert_eq!(actual, expected);
}

#[test]
fn deserialize_custom_state_event() {
    let json_data = custom_state_event();

    let expected_content = json!({
        "m.relates_to": {
            "event_id": "$MDitXXXXXX",
            "key": "👍",
            "rel_type": "m.annotation"
        }
    });

    assert_matches!(
        from_json_value::<Raw<AnyStateEvent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap(),
        AnyStateEvent::Custom(StateEvent {
            content: CustomEventContent {
                json, event_type,
            },
            event_id,
            origin_server_ts,
            sender,
            room_id,
            prev_content: None,
            state_key,
            unsigned,
        }) if json == expected_content && event_type == "m.reaction"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && room_id == room_id!("!room:room.com")
            && state_key == ""
            && !unsigned.is_empty()
    );
}

#[test]
fn deserialize_custom_state_sync_event() {
    let json_data = custom_state_event();

    let expected_content = json!({
        "m.relates_to": {
            "event_id": "$MDitXXXXXX",
            "key": "👍",
            "rel_type": "m.annotation"
        }
    });

    assert_matches!(
        from_json_value::<SyncStateEvent<AnyStateEventContent>>(json_data)
            .unwrap(),
        SyncStateEvent {
            content: AnyStateEventContent::Custom(CustomEventContent {
                json, event_type,
            }),
            event_id,
            origin_server_ts,
            sender,
            prev_content: None,
            state_key,
            unsigned,
        } if json == expected_content && event_type == "m.reaction"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && state_key == ""
            && !unsigned.is_empty()
    );
}

#[test]
fn deserialize_custom_message_sync_event() {
    let json_data = json!({
        "content": {
            "body": "👍"
        },
        "event_id": "$h29iv0s8:example.com",
        "origin_server_ts": 10,
        "room_id": "!room:room.com",
        "sender": "@carl:example.com",
        "type": "m.ruma_custom",
        "unsigned": {
            "age": 85
        }
    });

    let expected_content = json!({
        "body": "👍",
    });

    assert_matches!(
        from_json_value::<AnySyncRoomEvent>(json_data)
            .unwrap(),
        AnySyncRoomEvent::Message(AnySyncMessageEvent::Custom(SyncMessageEvent {
            content: CustomEventContent {
                json, event_type,
            },
            event_id,
            origin_server_ts,
            sender,
            unsigned,
        })) if json == expected_content && event_type == "m.ruma_custom"
            && event_id == event_id!("$h29iv0s8:example.com")
            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10)
            && sender == user_id!("@carl:example.com")
            && !unsigned.is_empty()
    );
}
<|endoftext|>


<commit_before>#![deny(
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

extern crate clap;
extern crate glob;
#[macro_use] extern crate log;
extern crate semver;
extern crate toml;
#[macro_use] extern crate version;

extern crate libimagrt;
extern crate libimagstore;
extern crate libimagentryview;
#[macro_use] extern crate libimagerror;

use std::result::Result as RResult;
use std::process::exit;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagstore::store::FileLockEntry;
use libimagerror::trace::{trace_error, trace_error_exit};

mod error;
mod ui;
mod viewer;

use error::{ViewError, ViewErrorKind};
use ui::build_ui;
use viewer::Viewer;
use viewer::ViewInformation;
use viewer::stdout::StdoutViewer;

type Result<T> = RResult<T, ViewError>;

fn main() {
    let rt = generate_runtime_setup( "imag-view",
                                     &version!()[..],
                                     "View entries (readonly)",
                                     build_ui);

    let entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap

    if entry_id.contains("~") {
        error!("The --id argument does not need the version part");
        exit(1);
    }

    if rt.cli().is_present("versions") {
        if let Err(e) = view_versions_of(entry_id, &rt) {
            trace_error_exit(&e, 1);
        }
    } else {
        let entry_version   = rt.cli().value_of("version");
        let view_header     = rt.cli().is_present("view-header");
        let view_content    = rt.cli().is_present("view-content");

        let scmd = rt.cli().subcommand_matches("view-in");
        if scmd.is_none() {
            debug!("No commandline call");
            exit(1); // we can afford not-executing destructors here
        }
        let scmd = scmd.unwrap();

        let viewer = {
            if scmd.is_present("view-in-stdout") {
                Box::new(StdoutViewer::new())
            } else if scmd.is_present("view-in-ui") {
                warn!("Viewing in UI is currently not supported, switch to stdout");
                Box::new(StdoutViewer::new())
            } else if scmd.is_present("view-in-browser") {
                warn!("Viewing in browser is currently not supported, switch to stdout");
                Box::new(StdoutViewer::new())
            } else if scmd.is_present("view-in-texteditor") {
                warn!("Viewing in texteditor is currently not supported, switch to stdout");
                Box::new(StdoutViewer::new())
            } else if scmd.is_present("view-in-custom") {
                warn!("Viewing in custom is currently not supported, switch to stdout");
                Box::new(StdoutViewer::new())
            } else {
                Box::new(StdoutViewer::new())
            }
        };

        let entry = load_entry(entry_id, entry_version, &rt);
        if entry.is_err() {
            trace_error_exit(&entry.unwrap_err(), 1);
        }
        let entry = entry.unwrap();

        let view_info = ViewInformation {
            entry:          entry,
            view_header:    view_header,
            view_content:   view_content,
            view_copy:      view_copy,
            keep_copy:      keep_copy,
        };

        viewer.view(view_info);
    }
}

// TODO: This is a shameless adaption of imag-store/src/util.rs
fn load_entry<'a>(id: &str,
                  version: Option<&str>,
                  rt: &'a Runtime)
    -> Result<FileLockEntry<'a>>
{
    debug!("Checking path element for version");

    let version = {
        if version.is_none() {
            let r = id.split('~').last();
            if r.is_none() {
                warn!("No version");
                return Err(ViewError::new(ViewErrorKind::NoVersion, None));
            } else {
                r.unwrap()
            }
        } else {
            version.unwrap()
        }
    };

    debug!("Building path from {:?} and {:?}", id, version);
    let mut path = rt.store().path().clone();

    if id.starts_with('/') {
        path.push(format!("{}~{}", &id[1..id.len()], version));
    } else {
        path.push(format!("{}~{}", id, version));
    }

    // the above is the adaption...

    rt.store().retrieve(path)
        .map_err(|e| ViewError::new(ViewErrorKind::StoreError, Some(Box::new(e))))
}

fn view_versions_of(id: &str, rt: &Runtime) -> Result<()> {
    use glob::glob;

    let mut path = rt.store().path().clone();

    if id.starts_with('/') {
        path.push(format!("{}~*", &id[1..id.len()]));
    } else {
        path.push(format!("{}~*", id));
    }

    if let Some(path) = path.to_str() {
        match glob(path) {
            Ok(paths) => {
                for entry in paths {
                    match entry {
                        Ok(path) => println!("{}", path.file_name().and_then(|s| s.to_str()).unwrap()),
                        Err(e)   => trace_error(e.error()),
                    }
                }
                Ok(())
            },
            Err(e) => {
                debug!("Error in pattern");
                Err(ViewError::new(ViewErrorKind::PatternError, Some(Box::new(e))))
            },
        }
    } else {
        warn!("Could not build glob() argument!");
        Err(ViewError::new(ViewErrorKind::GlobBuildError, None))
    }
}

<commit_msg>Move to use libimagentryview for viewing entries<commit_after>#![deny(
    non_camel_case_types,
    non_snake_case,
    path_statements,
    trivial_numeric_casts,
    unstable_features,
    unused_allocation,
    unused_import_braces,
    unused_imports,
    unused_must_use,
    unused_mut,
    unused_qualifications,
    while_true,
)]

extern crate clap;
extern crate glob;
#[macro_use] extern crate log;
extern crate semver;
extern crate toml;
#[macro_use] extern crate version;

extern crate libimagrt;
extern crate libimagstore;
extern crate libimagentryview;
#[macro_use] extern crate libimagerror;

use std::result::Result as RResult;
use std::process::exit;

use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagstore::store::FileLockEntry;
use libimagerror::trace::{trace_error, trace_error_exit};
use libimagentryview::builtin::stdout::StdoutViewer;
use libimagentryview::viewer::Viewer;

mod error;
mod ui;

use error::{ViewError, ViewErrorKind};
use ui::build_ui;

type Result<T> = RResult<T, ViewError>;

fn main() {
    let rt = generate_runtime_setup( "imag-view",
                                     &version!()[..],
                                     "View entries (readonly)",
                                     build_ui);

    let entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap

    if entry_id.contains("~") {
        error!("The --id argument does not need the version part");
        exit(1);
    }

    if rt.cli().is_present("versions") {
        if let Err(e) = view_versions_of(entry_id, &rt) {
            trace_error_exit(&e, 1);
        }
    } else {
        let entry_version   = rt.cli().value_of("version");
        let view_header     = rt.cli().is_present("view-header");
        let view_content    = rt.cli().is_present("view-content");

        let scmd = rt.cli().subcommand_matches("view-in");
        if scmd.is_none() {
            debug!("No commandline call");
            exit(1); // we can afford not-executing destructors here
        }
        let scmd = scmd.unwrap();

        let viewer = {
            if scmd.is_present("view-in-stdout") {
            } else if scmd.is_present("view-in-ui") {
                warn!("Viewing in UI is currently not supported, switch to stdout");
            } else if scmd.is_present("view-in-browser") {
                warn!("Viewing in browser is currently not supported, switch to stdout");
            } else if scmd.is_present("view-in-texteditor") {
                warn!("Viewing in texteditor is currently not supported, switch to stdout");
            } else if scmd.is_present("view-in-custom") {
                warn!("Viewing in custom is currently not supported, switch to stdout");
            }

            StdoutViewer::new(view_header, view_content)
        };

        let entry = load_entry(entry_id, entry_version, &rt);
        if entry.is_err() {
            trace_error_exit(&entry.unwrap_err(), 1);
        }
        let entry = entry.unwrap();

        if let Err(e) = viewer.view_entry(&entry) {
            trace_error(&e);
            exit(1);
        }
    }
}

// TODO: This is a shameless adaption of imag-store/src/util.rs
fn load_entry<'a>(id: &str,
                  version: Option<&str>,
                  rt: &'a Runtime)
    -> Result<FileLockEntry<'a>>
{
    debug!("Checking path element for version");

    let version = {
        if version.is_none() {
            let r = id.split('~').last();
            if r.is_none() {
                warn!("No version");
                return Err(ViewError::new(ViewErrorKind::NoVersion, None));
            } else {
                r.unwrap()
            }
        } else {
            version.unwrap()
        }
    };

    debug!("Building path from {:?} and {:?}", id, version);
    let mut path = rt.store().path().clone();

    if id.starts_with('/') {
        path.push(format!("{}~{}", &id[1..id.len()], version));
    } else {
        path.push(format!("{}~{}", id, version));
    }

    // the above is the adaption...

    rt.store().retrieve(path)
        .map_err(|e| ViewError::new(ViewErrorKind::StoreError, Some(Box::new(e))))
}

fn view_versions_of(id: &str, rt: &Runtime) -> Result<()> {
    use glob::glob;

    let mut path = rt.store().path().clone();

    if id.starts_with('/') {
        path.push(format!("{}~*", &id[1..id.len()]));
    } else {
        path.push(format!("{}~*", id));
    }

    if let Some(path) = path.to_str() {
        match glob(path) {
            Ok(paths) => {
                for entry in paths {
                    match entry {
                        Ok(path) => println!("{}", path.file_name().and_then(|s| s.to_str()).unwrap()),
                        Err(e)   => trace_error(e.error()),
                    }
                }
                Ok(())
            },
            Err(e) => {
                debug!("Error in pattern");
                Err(ViewError::new(ViewErrorKind::PatternError, Some(Box::new(e))))
            },
        }
    } else {
        warn!("Could not build glob() argument!");
        Err(ViewError::new(ViewErrorKind::GlobBuildError, None))
    }
}

<|endoftext|>


<commit_before>// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Management of styles.

use std::collections::HashMap;

use serde_json::{self, Value};
use syntect::highlighting::StyleModifier as SynStyleModifier;
use syntect::highlighting::{Color, Theme, ThemeSet, ThemeSettings, Highlighter, BLACK};

const N_RESERVED_STYLES: usize = 2;
const SYNTAX_PRIORITY_DEFAULT: u16 = 200;
const SYNTAX_PRIORITY_LOWEST: u16 = 0;

#[derive(Clone, PartialEq, Eq, Default, Hash, Debug, Serialize, Deserialize)]
/// A mergeable style. All values except priority are optional.
///
/// Note: A `None` value represents the absense of preference; in the case of
/// boolean options, `Some(false)` means that this style will override a lower
/// priority value in the same field.
pub struct Style {
    /// The priority of this style, in the range (0, 1000). Used to resolve
    /// conflicting fields when merging styles. The higher priority wins.
    #[serde(skip_serializing)]
    pub priority: u16,
    /// The foreground text color, in ARGB.
    pub fg_color: Option<u32>,
    /// The background text color, in ARGB.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bg_color: Option<u32>,
    /// The font-weight, in the range 100-900, interpreted like the CSS
    /// font-weight property.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub weight: Option<u16>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub underline: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub italic: Option<bool>,
}

impl Style {

    /// Creates a new `Style` by converting from a `Syntect::StyleModifier`.
    pub fn from_syntect_style_mod(style: &SynStyleModifier) -> Self {
        let font_style = style.font_style.map(|s|s.bits()).unwrap_or_default();
        let weight = if (font_style & 1) != 0 { Some(700) } else { None };
        let underline = if (font_style & 2) != 0 { Some(true) } else { None };
        let italic = if (font_style & 4) != 0 { Some(true) } else { None };

        Self::new(
            SYNTAX_PRIORITY_DEFAULT,
            style.foreground.map(|c| Self::rgba_from_syntect_color(&c)),
            None,
            //TODO: stop ignoring background color
            //style.background.map(|c| Self::rgba_from_syntect_color(&c)),
            weight,
            underline,
            italic,
            )
    }

    pub fn new<O32, O16, OB>(priority: u16, fg_color: O32, bg_color: O32,
                             weight: O16, underline: OB, italic: OB) -> Self
        where O32: Into<Option<u32>>,
              O16: Into<Option<u16>>,
              OB: Into<Option<bool>>
    {
        assert!(priority <= 1000);
        Style {
            priority: priority,
            fg_color: fg_color.into(),
            bg_color: bg_color.into(),
            weight: weight.into(),
            underline: underline.into(),
            italic: italic.into(),
        }
    }

    /// Returns the default style for the given `Theme`.
    pub fn default_for_theme(theme: &Theme) -> Self {
        let fg = theme.settings.foreground.unwrap_or(BLACK);
        Style::new(
            SYNTAX_PRIORITY_LOWEST,
            Some(Self::rgba_from_syntect_color(&fg)),
            None,
            None,
            None,
            None)
    }

    /// Creates a new style by combining attributes of `self` and `other`.
    /// If both styles define an attribute, the highest priority wins; `other`
    /// wins in the case of a tie.
    ///
    /// Note: when merging multiple styles, apply them in increasing priority.
    pub fn merge(&self, other: &Style) -> Style {
        let (p1, p2) = if self.priority > other.priority {
            (self, other)
        } else {
            (other, self)
        };

        Style::new(
            p1.priority,
            p1.fg_color.or(p2.fg_color),
            //TODO: stop ignoring background color
            None,
            p1.weight.or(p2.weight),
            p1.underline.or(p2.underline),
            p1.italic.or(p2.italic),
            )
    }

    /// Encode this `Style`, setting the `id` property.
    ///
    /// Note: this should only be used when sending the `def_style` RPC.
    pub fn to_json(&self, id: usize) -> Value {
        let mut as_val = serde_json::to_value(self).expect("failed to encode style");
        as_val["id"] = serde_json::to_value(id).unwrap();
        as_val
    }

    fn rgba_from_syntect_color(color: &Color) -> u32 {
        let &Color { r, g, b, a } = color;
        ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
    }
}

/// A map from styles to client identifiers for a given `Theme`.
pub struct ThemeStyleMap {
    themes: ThemeSet,
    theme_name: String,
    theme: Theme,
    default_style: Style,
    map: HashMap<Style, usize>,

    // It's not obvious we actually have to store the style, we seem to only need it
    // as the key in the map.
    styles: Vec<Style>,
}

impl ThemeStyleMap {
    pub fn new() -> ThemeStyleMap {
        let themes = ThemeSet::load_defaults();
        let theme_name = "InspiredGitHub".to_owned();
        let theme = themes.themes.get(&theme_name).expect("missing theme").to_owned();
        let default_style = Style::default_for_theme(&theme);

        ThemeStyleMap {
            themes: themes,
            theme_name: theme_name,
            theme: theme,
            default_style: default_style,
            map: HashMap::new(),
            styles: Vec::new(),
        }
    }

    pub fn get_default_style(&self) -> &Style {
        &self.default_style
    }

    pub fn get_highlighter<'a>(&'a self) -> Highlighter<'a> {
        Highlighter::new(&self.theme)
    }

    pub fn get_theme_name(&self) -> &str {
        &self.theme_name
    }

    pub fn get_theme_settings<'a>(&'a self) -> &ThemeSettings {
        &self.theme.settings
    }

    pub fn get_theme_names(&self) -> Vec<String>  {
        self.themes.themes.keys().cloned().collect()
    }

    pub fn set_theme(&mut self, theme_name: &str) -> Result<(), &'static str> {
        if theme_name == self.theme_name {
            return Ok(())
        }
        if let Some(new_theme) = self.themes.themes.get(theme_name) {
            self.theme = new_theme.to_owned();
            self.theme_name = theme_name.to_owned();
            self.default_style = Style::default_for_theme(&self.theme);
            self.map = HashMap::new();
            self.styles = Vec::new();
            Ok(())
        } else {
        Err("unknown theme")
        }
    }

    pub fn merge_with_default(&self, style: &Style) -> Style {
        self.default_style.merge(style)
    }

    pub fn lookup(&self, style: &Style) -> Option<usize> {
        self.map.get(style).map(|&ix| ix)
    }

    pub fn add(&mut self, style: &Style) -> usize {
        let result = self.styles.len() + N_RESERVED_STYLES;
        self.map.insert(style.clone(), result);
        self.styles.push(style.clone());
        result
    }
}
<commit_msg>Fixed deprecations with BLACK<commit_after>// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Management of styles.

use std::collections::HashMap;

use serde_json::{self, Value};
use syntect::highlighting::StyleModifier as SynStyleModifier;
use syntect::highlighting::{Color, Theme, ThemeSet, ThemeSettings, Highlighter};

const N_RESERVED_STYLES: usize = 2;
const SYNTAX_PRIORITY_DEFAULT: u16 = 200;
const SYNTAX_PRIORITY_LOWEST: u16 = 0;

#[derive(Clone, PartialEq, Eq, Default, Hash, Debug, Serialize, Deserialize)]
/// A mergeable style. All values except priority are optional.
///
/// Note: A `None` value represents the absense of preference; in the case of
/// boolean options, `Some(false)` means that this style will override a lower
/// priority value in the same field.
pub struct Style {
    /// The priority of this style, in the range (0, 1000). Used to resolve
    /// conflicting fields when merging styles. The higher priority wins.
    #[serde(skip_serializing)]
    pub priority: u16,
    /// The foreground text color, in ARGB.
    pub fg_color: Option<u32>,
    /// The background text color, in ARGB.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bg_color: Option<u32>,
    /// The font-weight, in the range 100-900, interpreted like the CSS
    /// font-weight property.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub weight: Option<u16>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub underline: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub italic: Option<bool>,
}

impl Style {

    /// Creates a new `Style` by converting from a `Syntect::StyleModifier`.
    pub fn from_syntect_style_mod(style: &SynStyleModifier) -> Self {
        let font_style = style.font_style.map(|s|s.bits()).unwrap_or_default();
        let weight = if (font_style & 1) != 0 { Some(700) } else { None };
        let underline = if (font_style & 2) != 0 { Some(true) } else { None };
        let italic = if (font_style & 4) != 0 { Some(true) } else { None };

        Self::new(
            SYNTAX_PRIORITY_DEFAULT,
            style.foreground.map(|c| Self::rgba_from_syntect_color(&c)),
            None,
            //TODO: stop ignoring background color
            //style.background.map(|c| Self::rgba_from_syntect_color(&c)),
            weight,
            underline,
            italic,
            )
    }

    pub fn new<O32, O16, OB>(priority: u16, fg_color: O32, bg_color: O32,
                             weight: O16, underline: OB, italic: OB) -> Self
        where O32: Into<Option<u32>>,
              O16: Into<Option<u16>>,
              OB: Into<Option<bool>>
    {
        assert!(priority <= 1000);
        Style {
            priority: priority,
            fg_color: fg_color.into(),
            bg_color: bg_color.into(),
            weight: weight.into(),
            underline: underline.into(),
            italic: italic.into(),
        }
    }

    /// Returns the default style for the given `Theme`.
    pub fn default_for_theme(theme: &Theme) -> Self {
        let fg = theme.settings.foreground.unwrap_or(Color::BLACK);
        Style::new(
            SYNTAX_PRIORITY_LOWEST,
            Some(Self::rgba_from_syntect_color(&fg)),
            None,
            None,
            None,
            None)
    }

    /// Creates a new style by combining attributes of `self` and `other`.
    /// If both styles define an attribute, the highest priority wins; `other`
    /// wins in the case of a tie.
    ///
    /// Note: when merging multiple styles, apply them in increasing priority.
    pub fn merge(&self, other: &Style) -> Style {
        let (p1, p2) = if self.priority > other.priority {
            (self, other)
        } else {
            (other, self)
        };

        Style::new(
            p1.priority,
            p1.fg_color.or(p2.fg_color),
            //TODO: stop ignoring background color
            None,
            p1.weight.or(p2.weight),
            p1.underline.or(p2.underline),
            p1.italic.or(p2.italic),
            )
    }

    /// Encode this `Style`, setting the `id` property.
    ///
    /// Note: this should only be used when sending the `def_style` RPC.
    pub fn to_json(&self, id: usize) -> Value {
        let mut as_val = serde_json::to_value(self).expect("failed to encode style");
        as_val["id"] = serde_json::to_value(id).unwrap();
        as_val
    }

    fn rgba_from_syntect_color(color: &Color) -> u32 {
        let &Color { r, g, b, a } = color;
        ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
    }
}

/// A map from styles to client identifiers for a given `Theme`.
pub struct ThemeStyleMap {
    themes: ThemeSet,
    theme_name: String,
    theme: Theme,
    default_style: Style,
    map: HashMap<Style, usize>,

    // It's not obvious we actually have to store the style, we seem to only need it
    // as the key in the map.
    styles: Vec<Style>,
}

impl ThemeStyleMap {
    pub fn new() -> ThemeStyleMap {
        let themes = ThemeSet::load_defaults();
        let theme_name = "InspiredGitHub".to_owned();
        let theme = themes.themes.get(&theme_name).expect("missing theme").to_owned();
        let default_style = Style::default_for_theme(&theme);

        ThemeStyleMap {
            themes: themes,
            theme_name: theme_name,
            theme: theme,
            default_style: default_style,
            map: HashMap::new(),
            styles: Vec::new(),
        }
    }

    pub fn get_default_style(&self) -> &Style {
        &self.default_style
    }

    pub fn get_highlighter<'a>(&'a self) -> Highlighter<'a> {
        Highlighter::new(&self.theme)
    }

    pub fn get_theme_name(&self) -> &str {
        &self.theme_name
    }

    pub fn get_theme_settings<'a>(&'a self) -> &ThemeSettings {
        &self.theme.settings
    }

    pub fn get_theme_names(&self) -> Vec<String>  {
        self.themes.themes.keys().cloned().collect()
    }

    pub fn set_theme(&mut self, theme_name: &str) -> Result<(), &'static str> {
        if theme_name == self.theme_name {
            return Ok(())
        }
        if let Some(new_theme) = self.themes.themes.get(theme_name) {
            self.theme = new_theme.to_owned();
            self.theme_name = theme_name.to_owned();
            self.default_style = Style::default_for_theme(&self.theme);
            self.map = HashMap::new();
            self.styles = Vec::new();
            Ok(())
        } else {
        Err("unknown theme")
        }
    }

    pub fn merge_with_default(&self, style: &Style) -> Style {
        self.default_style.merge(style)
    }

    pub fn lookup(&self, style: &Style) -> Option<usize> {
        self.map.get(style).map(|&ix| ix)
    }

    pub fn add(&mut self, style: &Style) -> usize {
        let result = self.styles.len() + N_RESERVED_STYLES;
        self.map.insert(style.clone(), result);
        self.styles.push(style.clone());
        result
    }
}
<|endoftext|>