<commit_before>//! Implementations of the default callbacks exposed by wlc.
//! These functions are the main entry points into Way Cooler from user action.
//! This is the default mode that Way Cooler is in at initilization
use std::process::{Command};
use std::path::PathBuf;
use std::io;
use rustwlc::*;
use rustwlc::wayland::wlc_view_get_wl_client;
use rustwlc::input::pointer;
use uuid::Uuid;
use wayland_sys::server::wl_client;

use super::{Mode, Modes, Default, write_current_mode};
use super::{EVENT_BLOCKED, EVENT_PASS_THROUGH};
use ::render::screen_scrape::{read_screen_scrape_lock, scraped_pixels_lock,
                              sync_scrape};
use ::layout::try_lock_tree;
use ::registry;

use rustwlc::{ResizeEdge, WlcView, Geometry, Point};
use rustwlc::types::VIEW_ACTIVATED;
use nix::libc::pid_t;

/// A lock screen program, that has been spawned by Way Cooler.
///
/// The PID is what is returned from spawning the subprocess.
///
/// The view is optional, because after spawning it we must find it in the
/// `view_created` callback.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LockScreen {
    /// Lists of clients that want to lock the screen.
    ///
    /// Multiple clients can attempt to lock a screen.
    /// The normal mode of operation is that they are the same client,
    /// but with different connections.
    ///
    ///The usize is the client id, and the view is the view it's associated with
    /// (if it has been spawned yet)
    ///
    /// The WlcOutput is the output the view should be associated with.
    pub clients: Vec<(usize, WlcOutput, Option<WlcView>)>,
    /// Flag that is set when a view is destroyed, either because the user
    /// closed it OR if the program failed to spawn.
    removed: bool
}

impl LockScreen {
    /// Creates a new Lock Screen from the PID
    ///
    /// The view is set to `None`, because we haven't found it yet in the
    /// `view_created` callback. This should be updated once it is found,
    /// so that we can start locking the screen.
    pub fn new(client: *mut wl_client, output: WlcOutput) -> Self {
        LockScreen {
            clients: vec![(client as _, output, None)],
            removed: false
        }
    }

    /// Adds the view to the `LockScreen` if it's PID matches the stored one.
    ///
    /// If there is already here, false is always returned.
    ///
    /// If the view's PID matches this PID, then it's automatically focused,
    /// and the geometry of it is set to the size of it's `WlcOutput`'s size.
    fn add_view_if_match(&mut self, view: WlcView) -> bool {
        let cur_client = unsafe {
            wlc_view_get_wl_client(view.0 as _) as _
        };
        warn!("Checkng view {:#?}", view);
        for client in &mut self.clients {
            if client.0 == cur_client && client.2 == None {
                error!("Found a match, setting to output {:#?}", client.1);
                view.set_output(client.1);
                client.2 = Some(view);
                let output = view.get_output();
                let resolution = output.get_resolution()
                    .expect("Output was invalid: had no resolution");
                let geo = Geometry {
                    origin: Point { x: 0, y: 0 },
                    size: resolution
                };
                view.set_geometry(ResizeEdge::empty(), geo);
                view.set_state(VIEW_ACTIVATED, true);
                view.focus();
                view.set_mask(1);
                view.bring_to_front();
                return true
            }
        }
        false
    }

    /// Removes the lockscreen if provided view matches the stored view.
    ///
    /// Does not compare the PID, because the passed in `WlcView` should be
    /// considered invalidated because this function is probably being called
    /// from `view_destroyed`.
    ///
    /// To protect against failing programs, the child handler should be
    /// checked to see if the program is still running.
    /// HOWEVER, this is blocked because this is still a nightly feature.
    /// See https://github.com/rust-lang/rust/issues/38903
    fn remove_if_match(&mut self, cur_view: WlcView) -> Option<WlcView> {
        for &mut (_, _, ref mut view) in &mut self.clients {
            if *view == Some(cur_view) {
                self.removed = true;
                // invalidate this view so we can't do anything to it.
                return view.take()
            }
        }
        None
    }

    fn eval_if_lockscreen<F, T>(&mut self, cur_view: WlcView, eval: F) -> T
        where F: Fn(WlcView) -> T,
              T: ::std::default::Default
    {
        for &(_, _, ref view) in &self.clients {
            if *view == Some(cur_view) {
                return eval(view.unwrap())
            }
        }
        T::default()
    }
}

#[allow(unused)]
impl Mode for LockScreen {
    fn output_render_post(&mut self, output: WlcOutput) {
        Default.output_render_post(output);
        // TODO Uncomment
        // we should allow this some other way
        /*// Don't allow screen scraping during lock screen
        let need_to_fetch = read_screen_scrape_lock();
        if *need_to_fetch {
            if let Ok(mut scraped_pixels) = scraped_pixels_lock() {
                let resolution = output.get_resolution()
                    .expect("Output had no resolution");
                // Give them zero-ed out pixels
                *scraped_pixels = vec![0; resolution.w as usize * resolution.h as usize * 4];
                sync_scrape();
            }
        }*/
    }

    fn view_created(&mut self, view: WlcView) -> bool {
        // this will focus and set the size if necessary.
        if self.add_view_if_match(view) {
            trace!("Adding lockscreen");
            true
        } else {
            false
        }
    }

    fn view_destroyed(&mut self, view: WlcView) {
        // Always remove the destroyed view from the tree.
        Default.view_destroyed(view);
        // View is automatically removed from the internal lock screen struct.
        if self.remove_if_match(view).is_some() {
            trace!("Removing lock screen");
            // Update the active container in the tree to the active path end.
            if let Ok(mut tree) = try_lock_tree() {
                tree.reset_focus()
                    .unwrap_or_else(|_| {
                        warn!("Could not reset focus\
                              after removing lock screen");
                    })
            }
            unlock_screen(self.clients[0].0 as _)
        }
    }

    fn view_focused(&mut self, current: WlcView, focused: bool) {
        // Do nothing
    }

    fn view_request_state(&mut self, view: WlcView, state: ViewState, toggle: bool) {
        // Do nothing
    }

    fn view_request_move(&mut self, view: WlcView, _dest: Point) {
        // Do nothing
    }

    fn view_request_resize(&mut self, view: WlcView, edge: ResizeEdge, point: Point) {
        // Do nothing
    }

    fn on_keyboard_key(&mut self, view: WlcView, _time: u32, mods: KeyboardModifiers,
                            key: u32, state: KeyState) -> bool {
        !self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_button(&mut self, view: WlcView, _time: u32,
                            mods: KeyboardModifiers, button: u32,
                                state: ButtonState, point: Point) -> bool {
        self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_scroll(&mut self, view: WlcView, _time: u32,
                            _mods_ptr: KeyboardModifiers, _axis: ScrollAxis,
                            _heights: [f64; 2]) -> bool {
        self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_motion(&mut self, view: WlcView, _time: u32, point: Point) -> bool {
        !self.eval_if_lockscreen(view, |view| {
            view.focus();
            pointer::set_position(point);
            // false because default is false, but true is the base case here
            false
        })
    }
}

#[derive(Debug)]
pub enum LockScreenErr {
    /// IO error while trying to lock screen.
    /// e.g: Couldn't open lock screen program.
    IO(io::Error),
    /// Lock screen was already locked.
    AlreadyLocked
}

pub fn lock_screen(client: *mut wl_client, output: WlcOutput) -> Result<(), LockScreenErr> {
    let mut mode = write_current_mode();
    warn!("Locking screen w/ mode {:#?}", mode);
    {
        match *mode {
            Modes::LockScreen(ref mut lock_mode) => {
                warn!("Found another view");
                lock_mode.clients.push((client as _, output, None));
                return Ok(())
            },
            _ => {}
        }
    }
    *mode = Modes::LockScreen(LockScreen::new(client, output));
    Ok(())
}

pub fn unlock_screen(client: *mut wl_client) {
    let mut mode = {
        write_current_mode().clone()
    };
    match mode {
        Modes::LockScreen(ref lock_mode) => {
            for &(_, _, view) in &lock_mode.clients {
                view.map(WlcView::close);
            }
        },
        _ => {}
    }
    *write_current_mode() = Modes::Default(Default);
}
<commit_msg>Don't unlock screen when the view dies, that it's job!<commit_after>//! Implementations of the default callbacks exposed by wlc.
//! These functions are the main entry points into Way Cooler from user action.
//! This is the default mode that Way Cooler is in at initilization
use std::process::{Command};
use std::path::PathBuf;
use std::io;
use rustwlc::*;
use rustwlc::wayland::wlc_view_get_wl_client;
use rustwlc::input::pointer;
use uuid::Uuid;
use wayland_sys::server::wl_client;

use super::{Mode, Modes, Default, write_current_mode};
use super::{EVENT_BLOCKED, EVENT_PASS_THROUGH};
use ::render::screen_scrape::{read_screen_scrape_lock, scraped_pixels_lock,
                              sync_scrape};
use ::layout::try_lock_tree;
use ::registry;

use rustwlc::{ResizeEdge, WlcView, Geometry, Point};
use rustwlc::types::VIEW_ACTIVATED;
use nix::libc::pid_t;

/// A lock screen program, that has been spawned by Way Cooler.
///
/// The PID is what is returned from spawning the subprocess.
///
/// The view is optional, because after spawning it we must find it in the
/// `view_created` callback.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LockScreen {
    /// Lists of clients that want to lock the screen.
    ///
    /// Multiple clients can attempt to lock a screen.
    /// The normal mode of operation is that they are the same client,
    /// but with different connections.
    ///
    ///The usize is the client id, and the view is the view it's associated with
    /// (if it has been spawned yet)
    ///
    /// The WlcOutput is the output the view should be associated with.
    pub clients: Vec<(usize, WlcOutput, Option<WlcView>)>,
    /// Flag that is set when a view is destroyed, either because the user
    /// closed it OR if the program failed to spawn.
    removed: bool
}

impl LockScreen {
    /// Creates a new Lock Screen from the PID
    ///
    /// The view is set to `None`, because we haven't found it yet in the
    /// `view_created` callback. This should be updated once it is found,
    /// so that we can start locking the screen.
    pub fn new(client: *mut wl_client, output: WlcOutput) -> Self {
        LockScreen {
            clients: vec![(client as _, output, None)],
            removed: false
        }
    }

    /// Adds the view to the `LockScreen` if it's PID matches the stored one.
    ///
    /// If there is already here, false is always returned.
    ///
    /// If the view's PID matches this PID, then it's automatically focused,
    /// and the geometry of it is set to the size of it's `WlcOutput`'s size.
    fn add_view_if_match(&mut self, view: WlcView) -> bool {
        let cur_client = unsafe {
            wlc_view_get_wl_client(view.0 as _) as _
        };
        warn!("Checkng view {:#?}", view);
        for client in &mut self.clients {
            if client.0 == cur_client && client.2 == None {
                error!("Found a match, setting to output {:#?}", client.1);
                view.set_output(client.1);
                client.2 = Some(view);
                let output = view.get_output();
                let resolution = output.get_resolution()
                    .expect("Output was invalid: had no resolution");
                let geo = Geometry {
                    origin: Point { x: 0, y: 0 },
                    size: resolution
                };
                view.set_geometry(ResizeEdge::empty(), geo);
                view.set_state(VIEW_ACTIVATED, true);
                view.focus();
                view.set_mask(1);
                view.bring_to_front();
                return true
            }
        }
        false
    }

    /// Removes the lockscreen if provided view matches the stored view.
    ///
    /// Does not compare the PID, because the passed in `WlcView` should be
    /// considered invalidated because this function is probably being called
    /// from `view_destroyed`.
    ///
    /// To protect against failing programs, the child handler should be
    /// checked to see if the program is still running.
    /// HOWEVER, this is blocked because this is still a nightly feature.
    /// See https://github.com/rust-lang/rust/issues/38903
    fn remove_if_match(&mut self, cur_view: WlcView) -> Option<WlcView> {
        for &mut (_, _, ref mut view) in &mut self.clients {
            if *view == Some(cur_view) {
                self.removed = true;
                // invalidate this view so we can't do anything to it.
                return view.take()
            }
        }
        None
    }

    fn eval_if_lockscreen<F, T>(&mut self, cur_view: WlcView, eval: F) -> T
        where F: Fn(WlcView) -> T,
              T: ::std::default::Default
    {
        for &(_, _, ref view) in &self.clients {
            if *view == Some(cur_view) {
                return eval(view.unwrap())
            }
        }
        T::default()
    }
}

#[allow(unused)]
impl Mode for LockScreen {
    fn output_render_post(&mut self, output: WlcOutput) {
        Default.output_render_post(output);
        // TODO Uncomment
        // we should allow this some other way
        /*// Don't allow screen scraping during lock screen
        let need_to_fetch = read_screen_scrape_lock();
        if *need_to_fetch {
            if let Ok(mut scraped_pixels) = scraped_pixels_lock() {
                let resolution = output.get_resolution()
                    .expect("Output had no resolution");
                // Give them zero-ed out pixels
                *scraped_pixels = vec![0; resolution.w as usize * resolution.h as usize * 4];
                sync_scrape();
            }
        }*/
    }

    fn view_created(&mut self, view: WlcView) -> bool {
        // this will focus and set the size if necessary.
        if self.add_view_if_match(view) {
            trace!("Adding lockscreen");
            true
        } else {
            false
        }
    }

    fn view_destroyed(&mut self, view: WlcView) {
        // Always remove the destroyed view from the tree.
        Default.view_destroyed(view);
        // View is automatically removed from the internal lock screen struct.
        if self.remove_if_match(view).is_some() {
            trace!("Removing lock screen");
            // Update the active container in the tree to the active path end.
            if let Ok(mut tree) = try_lock_tree() {
                tree.reset_focus()
                    .unwrap_or_else(|_| {
                        warn!("Could not reset focus\
                              after removing lock screen");
                    })
            }
        }
    }

    fn view_focused(&mut self, current: WlcView, focused: bool) {
        // Do nothing
    }

    fn view_request_state(&mut self, view: WlcView, state: ViewState, toggle: bool) {
        // Do nothing
    }

    fn view_request_move(&mut self, view: WlcView, _dest: Point) {
        // Do nothing
    }

    fn view_request_resize(&mut self, view: WlcView, edge: ResizeEdge, point: Point) {
        // Do nothing
    }

    fn on_keyboard_key(&mut self, view: WlcView, _time: u32, mods: KeyboardModifiers,
                            key: u32, state: KeyState) -> bool {
        !self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_button(&mut self, view: WlcView, _time: u32,
                            mods: KeyboardModifiers, button: u32,
                                state: ButtonState, point: Point) -> bool {
        self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_scroll(&mut self, view: WlcView, _time: u32,
                            _mods_ptr: KeyboardModifiers, _axis: ScrollAxis,
                            _heights: [f64; 2]) -> bool {
        self.eval_if_lockscreen(view, |view| {
            view.focus();
            true
        })
    }

    fn on_pointer_motion(&mut self, view: WlcView, _time: u32, point: Point) -> bool {
        !self.eval_if_lockscreen(view, |view| {
            view.focus();
            pointer::set_position(point);
            // false because default is false, but true is the base case here
            false
        })
    }
}

#[derive(Debug)]
pub enum LockScreenErr {
    /// IO error while trying to lock screen.
    /// e.g: Couldn't open lock screen program.
    IO(io::Error),
    /// Lock screen was already locked.
    AlreadyLocked
}

pub fn lock_screen(client: *mut wl_client, output: WlcOutput) -> Result<(), LockScreenErr> {
    let mut mode = write_current_mode();
    warn!("Locking screen w/ mode {:#?}", mode);
    {
        match *mode {
            Modes::LockScreen(ref mut lock_mode) => {
                warn!("Found another view");
                lock_mode.clients.push((client as _, output, None));
                return Ok(())
            },
            _ => {}
        }
    }
    *mode = Modes::LockScreen(LockScreen::new(client, output));
    Ok(())
}

pub fn unlock_screen(client: *mut wl_client) {
    let mut mode = {
        write_current_mode().clone()
    };
    match mode {
        Modes::LockScreen(ref lock_mode) => {
            for &(_, _, view) in &lock_mode.clients {
                view.map(WlcView::close);
            }
        },
        _ => {}
    }
    *write_current_mode() = Modes::Default(Default);
}
<|endoftext|>


<commit_before>use crate::console::{Console, ConsoleTextKind};
use crate::pipeline::{Pipeline, PipelineInfo};
use crate::pipeline_matcher::PathMatch;
use crate::util::{catch, decode_error, exit};
use crossbeam_channel::{Receiver, Sender};
use ctrlc;
use getch::Getch;
use memmap::Mmap;
use regex::Regex;
use std::fs::{self, File};
use std::io::{Error, Write};
use std::ops::Deref;
use std::str;
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;

// ---------------------------------------------------------------------------------------------------------------------
// PipelineReplacer
// ---------------------------------------------------------------------------------------------------------------------

pub struct PipelineReplacer {
    pub is_color: bool,
    pub is_interactive: bool,
    pub print_file: bool,
    pub print_column: bool,
    pub print_row: bool,
    pub infos: Vec<String>,
    pub errors: Vec<String>,
    console: Console,
    all_replace: bool,
    keyword: Vec<u8>,
    replacement: Vec<u8>,
    regex: bool,
    time_beg: Instant,
    time_bsy: Duration,
}

impl PipelineReplacer {
    pub fn new(keyword: &[u8], replacement: &[u8], regex: bool) -> Self {
        PipelineReplacer {
            is_color: true,
            is_interactive: true,
            print_file: true,
            print_column: false,
            print_row: false,
            infos: Vec::new(),
            errors: Vec::new(),
            console: Console::new(),
            all_replace: false,
            keyword: Vec::from(keyword),
            replacement: Vec::from(replacement),
            regex: regex,
            time_beg: Instant::now(),
            time_bsy: Duration::new(0, 0),
        }
    }

    fn replace_match(&mut self, pm: PathMatch) {
        if pm.matches.is_empty() {
            return;
        }
        self.console.is_color = self.is_color;

        let result = catch::<_, (), Error>(|| {
            let mut tmpfile = NamedTempFile::new_in(pm.path.parent().unwrap_or(&pm.path))?;

            let tmpfile_path = tmpfile.path().to_path_buf();
            let _ = ctrlc::set_handler(move || {
                let path = tmpfile_path.clone();
                let mut console = Console::new();
                console.write(
                    ConsoleTextKind::Info,
                    &format!("\nCleanup temporary file: {:?}\n", path),
                );
                let _ = fs::remove_file(path);
                exit(0, &mut console);
            });

            {
                let file = File::open(&pm.path)?;
                let mmap = unsafe { Mmap::map(&file) }?;
                let src = mmap.deref();

                let mut i = 0;
                let mut pos = 0;
                let mut column = 0;
                let mut last_lf = 0;
                for m in &pm.matches {
                    tmpfile.write_all(&src[i..m.beg])?;

                    let mut do_replace = true;
                    if self.is_interactive & !self.all_replace {
                        if self.print_file {
                            self.console.write(ConsoleTextKind::Filename, pm.path.to_str().unwrap());
                            self.console.write(ConsoleTextKind::Other, ": ");
                        }
                        if self.print_column | self.print_row {
                            while pos < m.beg {
                                if src[pos] == 0x0a {
                                    column += 1;
                                    last_lf = pos;
                                }
                                pos += 1;
                            }
                            if self.print_column {
                                self.console.write(ConsoleTextKind::Other, &format!("{}:", column + 1));
                            }
                            if self.print_row {
                                self.console
                                    .write(ConsoleTextKind::Other, &format!("{}:", m.beg - last_lf));
                            }
                        }

                        self.console.write_match_line(src, m);

                        let getch = Getch::new();
                        loop {
                            self.console
                                .write(ConsoleTextKind::Other, "Replace keyword? [Y]es/[n]o/[a]ll/[q]uit: ");
                            self.console.flush();
                            let key = char::from(getch.getch()?);
                            if key != '\n' {
                                self.console.write(ConsoleTextKind::Other, &format!("{}\n", key));
                            } else {
                                self.console.write(ConsoleTextKind::Other, "\n");
                            }
                            match key {
                                'Y' | 'y' | ' ' | '\n' => do_replace = true,
                                'N' | 'n' => do_replace = false,
                                'A' | 'a' => self.all_replace = true,
                                'Q' | 'q' => {
                                    let _ = tmpfile.close();
                                    exit(0, &mut self.console);
                                }
                                _ => continue,
                            }
                            break;
                        }
                    }

                    if do_replace {
                        if self.regex {
                            let replacement = self.get_regex_replacement(&src[m.beg..m.end]);
                            tmpfile.write_all(&replacement)?;
                        } else {
                            tmpfile.write_all(&self.replacement)?;
                        }
                    } else {
                        tmpfile.write_all(&src[m.beg..m.end])?;
                    }
                    i = m.end;
                }

                if i < src.len() {
                    tmpfile.write_all(&src[i..src.len()])?;
                }
                tmpfile.flush()?;
            }

            let real_path = fs::canonicalize(&pm.path)?;

            let metadata = fs::metadata(&real_path)?;

            fs::set_permissions(tmpfile.path(), metadata.permissions())?;
            tmpfile.persist(&real_path)?;

            Ok(())
        });
        match result {
            Ok(_) => (),
            Err(e) => self.console.write(
                ConsoleTextKind::Error,
                &format!("Error: {} @ {:?}\n", decode_error(e.kind()), pm.path),
            ),
        }
    }

    fn get_regex_replacement(&self, org: &[u8]) -> Vec<u8> {
        // All unwrap() is safe bacause keyword is already matched in pipeline_matcher
        let org = str::from_utf8(org).unwrap();
        let keyword = str::from_utf8(&self.keyword).unwrap();
        let replacement = str::from_utf8(&self.replacement).unwrap();
        let regex = Regex::new(&keyword).unwrap();
        let captures = regex.captures(&org).unwrap();

        let mut dst = String::new();
        captures.expand(&replacement, &mut dst);

        dst.into_bytes()
    }
}

impl Pipeline<PathMatch, ()> for PipelineReplacer {
    fn setup(&mut self, id: usize, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<()>>) {
        self.infos = Vec::new();
        self.errors = Vec::new();
        let mut seq_beg_arrived = false;

        loop {
            match rx.recv() {
                Ok(PipelineInfo::SeqDat(x, pm)) => {
                    watch_time!(self.time_bsy, {
                        self.replace_match(pm);
                        let _ = tx.send(PipelineInfo::SeqDat(x, ()));
                    });
                }

                Ok(PipelineInfo::SeqBeg(x)) => {
                    if !seq_beg_arrived {
                        self.time_beg = Instant::now();
                        let _ = tx.send(PipelineInfo::SeqBeg(x));
                        seq_beg_arrived = true;
                    }
                }

                Ok(PipelineInfo::SeqEnd(x)) => {
                    for i in &self.infos {
                        let _ = tx.send(PipelineInfo::MsgInfo(id, i.clone()));
                    }
                    for e in &self.errors {
                        let _ = tx.send(PipelineInfo::MsgErr(id, e.clone()));
                    }

                    let _ = tx.send(PipelineInfo::MsgTime(id, self.time_bsy, self.time_beg.elapsed()));
                    let _ = tx.send(PipelineInfo::SeqEnd(x));
                    break;
                }

                Ok(PipelineInfo::MsgInfo(i, e)) => {
                    let _ = tx.send(PipelineInfo::MsgInfo(i, e));
                }
                Ok(PipelineInfo::MsgErr(i, e)) => {
                    let _ = tx.send(PipelineInfo::MsgErr(i, e));
                }
                Ok(PipelineInfo::MsgTime(i, t0, t1)) => {
                    let _ = tx.send(PipelineInfo::MsgTime(i, t0, t1));
                }
                Err(_) => break,
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------------------------------
// Test
// ---------------------------------------------------------------------------------------------------------------------
<commit_msg>Fix enter detection #22<commit_after>use crate::console::{Console, ConsoleTextKind};
use crate::pipeline::{Pipeline, PipelineInfo};
use crate::pipeline_matcher::PathMatch;
use crate::util::{catch, decode_error, exit};
use crossbeam_channel::{Receiver, Sender};
use ctrlc;
use getch::Getch;
use memmap::Mmap;
use regex::Regex;
use std::fs::{self, File};
use std::io::{Error, Write};
use std::ops::Deref;
use std::str;
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;

// ---------------------------------------------------------------------------------------------------------------------
// PipelineReplacer
// ---------------------------------------------------------------------------------------------------------------------

pub struct PipelineReplacer {
    pub is_color: bool,
    pub is_interactive: bool,
    pub print_file: bool,
    pub print_column: bool,
    pub print_row: bool,
    pub infos: Vec<String>,
    pub errors: Vec<String>,
    console: Console,
    all_replace: bool,
    keyword: Vec<u8>,
    replacement: Vec<u8>,
    regex: bool,
    time_beg: Instant,
    time_bsy: Duration,
}

impl PipelineReplacer {
    pub fn new(keyword: &[u8], replacement: &[u8], regex: bool) -> Self {
        PipelineReplacer {
            is_color: true,
            is_interactive: true,
            print_file: true,
            print_column: false,
            print_row: false,
            infos: Vec::new(),
            errors: Vec::new(),
            console: Console::new(),
            all_replace: false,
            keyword: Vec::from(keyword),
            replacement: Vec::from(replacement),
            regex: regex,
            time_beg: Instant::now(),
            time_bsy: Duration::new(0, 0),
        }
    }

    fn replace_match(&mut self, pm: PathMatch) {
        if pm.matches.is_empty() {
            return;
        }
        self.console.is_color = self.is_color;

        let result = catch::<_, (), Error>(|| {
            let mut tmpfile = NamedTempFile::new_in(pm.path.parent().unwrap_or(&pm.path))?;

            let tmpfile_path = tmpfile.path().to_path_buf();
            let _ = ctrlc::set_handler(move || {
                let path = tmpfile_path.clone();
                let mut console = Console::new();
                console.write(
                    ConsoleTextKind::Info,
                    &format!("\nCleanup temporary file: {:?}\n", path),
                );
                let _ = fs::remove_file(path);
                exit(0, &mut console);
            });

            {
                let file = File::open(&pm.path)?;
                let mmap = unsafe { Mmap::map(&file) }?;
                let src = mmap.deref();

                let mut i = 0;
                let mut pos = 0;
                let mut column = 0;
                let mut last_lf = 0;
                for m in &pm.matches {
                    tmpfile.write_all(&src[i..m.beg])?;

                    let mut do_replace = true;
                    if self.is_interactive & !self.all_replace {
                        if self.print_file {
                            self.console.write(ConsoleTextKind::Filename, pm.path.to_str().unwrap());
                            self.console.write(ConsoleTextKind::Other, ": ");
                        }
                        if self.print_column | self.print_row {
                            while pos < m.beg {
                                if src[pos] == 0x0a {
                                    column += 1;
                                    last_lf = pos;
                                }
                                pos += 1;
                            }
                            if self.print_column {
                                self.console.write(ConsoleTextKind::Other, &format!("{}:", column + 1));
                            }
                            if self.print_row {
                                self.console
                                    .write(ConsoleTextKind::Other, &format!("{}:", m.beg - last_lf));
                            }
                        }

                        self.console.write_match_line(src, m);

                        let getch = Getch::new();
                        loop {
                            self.console
                                .write(ConsoleTextKind::Other, "Replace keyword? [Y]es/[n]o/[a]ll/[q]uit: ");
                            self.console.flush();
                            let key = char::from(getch.getch()?);
                            if key != '\n' {
                                self.console.write(ConsoleTextKind::Other, &format!("{}\n", key));
                            } else {
                                self.console.write(ConsoleTextKind::Other, "\n");
                            }
                            match key {
                                'Y' | 'y' | ' ' | '\r' | '\n' => do_replace = true,
                                'N' | 'n' => do_replace = false,
                                'A' | 'a' => self.all_replace = true,
                                'Q' | 'q' => {
                                    let _ = tmpfile.close();
                                    exit(0, &mut self.console);
                                }
                                _ => continue,
                            }
                            break;
                        }
                    }

                    if do_replace {
                        if self.regex {
                            let replacement = self.get_regex_replacement(&src[m.beg..m.end]);
                            tmpfile.write_all(&replacement)?;
                        } else {
                            tmpfile.write_all(&self.replacement)?;
                        }
                    } else {
                        tmpfile.write_all(&src[m.beg..m.end])?;
                    }
                    i = m.end;
                }

                if i < src.len() {
                    tmpfile.write_all(&src[i..src.len()])?;
                }
                tmpfile.flush()?;
            }

            let real_path = fs::canonicalize(&pm.path)?;

            let metadata = fs::metadata(&real_path)?;

            fs::set_permissions(tmpfile.path(), metadata.permissions())?;
            tmpfile.persist(&real_path)?;

            Ok(())
        });
        match result {
            Ok(_) => (),
            Err(e) => self.console.write(
                ConsoleTextKind::Error,
                &format!("Error: {} @ {:?}\n", decode_error(e.kind()), pm.path),
            ),
        }
    }

    fn get_regex_replacement(&self, org: &[u8]) -> Vec<u8> {
        // All unwrap() is safe bacause keyword is already matched in pipeline_matcher
        let org = str::from_utf8(org).unwrap();
        let keyword = str::from_utf8(&self.keyword).unwrap();
        let replacement = str::from_utf8(&self.replacement).unwrap();
        let regex = Regex::new(&keyword).unwrap();
        let captures = regex.captures(&org).unwrap();

        let mut dst = String::new();
        captures.expand(&replacement, &mut dst);

        dst.into_bytes()
    }
}

impl Pipeline<PathMatch, ()> for PipelineReplacer {
    fn setup(&mut self, id: usize, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<()>>) {
        self.infos = Vec::new();
        self.errors = Vec::new();
        let mut seq_beg_arrived = false;

        loop {
            match rx.recv() {
                Ok(PipelineInfo::SeqDat(x, pm)) => {
                    watch_time!(self.time_bsy, {
                        self.replace_match(pm);
                        let _ = tx.send(PipelineInfo::SeqDat(x, ()));
                    });
                }

                Ok(PipelineInfo::SeqBeg(x)) => {
                    if !seq_beg_arrived {
                        self.time_beg = Instant::now();
                        let _ = tx.send(PipelineInfo::SeqBeg(x));
                        seq_beg_arrived = true;
                    }
                }

                Ok(PipelineInfo::SeqEnd(x)) => {
                    for i in &self.infos {
                        let _ = tx.send(PipelineInfo::MsgInfo(id, i.clone()));
                    }
                    for e in &self.errors {
                        let _ = tx.send(PipelineInfo::MsgErr(id, e.clone()));
                    }

                    let _ = tx.send(PipelineInfo::MsgTime(id, self.time_bsy, self.time_beg.elapsed()));
                    let _ = tx.send(PipelineInfo::SeqEnd(x));
                    break;
                }

                Ok(PipelineInfo::MsgInfo(i, e)) => {
                    let _ = tx.send(PipelineInfo::MsgInfo(i, e));
                }
                Ok(PipelineInfo::MsgErr(i, e)) => {
                    let _ = tx.send(PipelineInfo::MsgErr(i, e));
                }
                Ok(PipelineInfo::MsgTime(i, t0, t1)) => {
                    let _ = tx.send(PipelineInfo::MsgTime(i, t0, t1));
                }
                Err(_) => break,
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------------------------------
// Test
// ---------------------------------------------------------------------------------------------------------------------
<|endoftext|>


<commit_before>use float::round;
use libc::types::os::arch::c95::c_double;
use css_colors::*;
use cmp::Eq;

pub struct Color { red : u8, green : u8, blue : u8, alpha : float}

impl Color : Eq {
    pure fn eq(&self, other: &Color) -> bool {
        return self.red == other.red && self.green == other.green && self.blue == other.blue &&
               self.alpha == other.alpha;
    }
    pure fn ne(&self, other: &Color) -> bool {
        !self.eq(other)
    }
}

pub fn rgba(r : u8, g : u8, b : u8, a : float) -> Color {
    Color { red : r, green : g, blue : b, alpha : a}
}

pub fn rgb(r : u8, g : u8, b : u8) -> Color {
    return rgba(r, g, b, 1.0);
}

pub fn hsla(h : float, s : float, l : float, a : float) -> Color {
    // Algorithm for converting hsl to rbg taken from
    // http://www.w3.org/TR/2003/CR-css3-color-20030514/#hsl-color
    let m2 = if l <= 0.5 { l*(s + 1.0) } else { l + s - l*s };
    let m1 = l*2.0 - m2;
    let h = h / 360.0;

    fn hue_to_rgb(m1 : float, m2 : float, h : float) -> float {
        let h = if h < 0.0 { h + 1.0 } else if h > 1.0 { h - 1.0 } else { h };

        match h {
          0.0 .. 1.0/6.0 => m1 + (m2 - m1)*h*6.0,
          1.0/6.0 .. 1.0/2.0 => m2,
          1.0/2.0 .. 2.0/3.0 => m1 + (m2 - m1)*(4.0 - 6.0*h),
          2.0/3.0 .. 1.0 => return m1,
          _ => fail ~"unexpected hue value"
        }
    }

    let r = round(255.0*hue_to_rgb(m1, m2, h + 1.0/3.0) as c_double);;
    let g = round(255.0*hue_to_rgb(m1, m2, h) as c_double);
    let b = round(255.0*hue_to_rgb(m1, m2, h - 1.0/3.0) as c_double);

    return rgba(r as u8, g as u8, b as u8, a);
}

pub fn hsl(h : float, s : float, l : float) -> Color {
    return hsla(h, s, l, 1.0);
}

impl Color {
    fn print() -> ~str {
        fmt!("rgba(%u,%u,%u,%f)", self.red as uint, self.green as uint,
             self.blue as uint, self.alpha)
    }
}

pub mod parsing {
    export parse_color;

    fn fail_unrecognized(col : &str) -> Option<Color> {
        warn!("Unrecognized color %s", col);
        return None;
    }

    /** Match an exact color keyword. */
    fn parse_by_name(color : &str) -> Option<Color> {
        let col = match color.to_lower() {
          ~"black" => black(),
          ~"silver" => silver(),
          ~"gray" => gray(),
          ~"grey" => gray(),
          ~"white" => white(),
          ~"maroon" => maroon(),
          ~"red" => red(),
          ~"purple" => purple(),
          ~"fuchsia" => fuchsia(),
          ~"green" => green(),
          ~"lime" => lime(),
          ~"olive" => olive(),
          ~"yellow" => yellow(),
          ~"navy" => navy(),
          ~"blue" => blue(),
          ~"teal" => teal(),
          ~"aqua" => aqua(),
          _ => return fail_unrecognized(color)
        };

        return Some(col);
    }

    /** Parses a color specification in the form rgb(foo,bar,baz) */
    fn parse_rgb(color : &str) -> Option<Color> {
        // Shave off the rgb( and the )
        let only_colors = color.substr(4u, color.len() - 5u);

        // split up r, g, and b
        let cols = only_colors.split_char(',');
        if cols.len() != 3u { return fail_unrecognized(color); }

        match (u8::from_str(cols[0]), u8::from_str(cols[1]),
             u8::from_str(cols[2])) {
          (Some(r), Some(g), Some(b)) => { Some(rgb(r, g, b)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form rgba(foo,bar,baz,qux) */
    fn parse_rgba(color : &str) -> Option<Color> {
        // Shave off the rgba( and the )
        let only_vals = color.substr(5u, color.len() - 6u);

        // split up r, g, and b
        let cols = only_vals.split_char(',');
        if cols.len() != 4u { return fail_unrecognized(color); }

        match (u8::from_str(cols[0]), u8::from_str(cols[1]),
             u8::from_str(cols[2]), float::from_str(cols[3])) {
          (Some(r), Some(g), Some(b), Some(a)) => { Some(rgba(r, g, b, a)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form hsl(foo,bar,baz) */
    fn parse_hsl(color : &str) -> Option<Color> {
        // Shave off the hsl( and the )
        let only_vals = color.substr(4u, color.len() - 5u);

        // split up h, s, and l
        let vals = only_vals.split_char(',');
        if vals.len() != 3u { return fail_unrecognized(color); }

        match (float::from_str(vals[0]), float::from_str(vals[1]),
             float::from_str(vals[2])) {
          (Some(h), Some(s), Some(l)) => { Some(hsl(h, s, l)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form hsla(foo,bar,baz,qux) */
    fn parse_hsla(color : &str) -> Option<Color> {
        // Shave off the hsla( and the )
        let only_vals = color.substr(5u, color.len() - 6u);

        let vals = only_vals.split_char(',');
        if vals.len() != 4u { return fail_unrecognized(color); }

        match (float::from_str(vals[0]), float::from_str(vals[1]),
             float::from_str(vals[2]), float::from_str(vals[3])) {
          (Some(h), Some(s), Some(l), Some(a)) => { Some(hsla(h, s, l, a)) }
          _ => { fail_unrecognized(color) }
        }
    }

    // Currently colors are supported in rgb(a,b,c) form and also by
    // keywords for several common colors.
    // TODO: extend this
    fn parse_color(color : &str) -> Option<Color> {
        match color {
          c if c.starts_with("rgb(") => parse_rgb(c),
          c if c.starts_with("rgba(") => parse_rgba(c),
          c if c.starts_with("hsl(") => parse_hsl(c),
          c if c.starts_with("hsla(") => parse_hsla(c),
          c => parse_by_name(c)
        }
    }
}

#[cfg(test)]
mod test {
    use css_colors::*;
    use option::unwrap;
    use parsing::parse_color;

    #[test]
    fn test_parse_by_name() {
        assert red().eq(&unwrap(parse_color(~"red")));
        assert lime().eq(&unwrap(parse_color(~"Lime")));
        assert blue().eq(&unwrap(parse_color(~"BLUE")));
        assert green().eq(&unwrap(parse_color(~"GreEN")));
        assert white().eq(&unwrap(parse_color(~"white")));
        assert black().eq(&unwrap(parse_color(~"Black")));
        assert gray().eq(&unwrap(parse_color(~"Gray")));
        assert silver().eq(&unwrap(parse_color(~"SiLvEr")));
        assert maroon().eq(&unwrap(parse_color(~"maroon")));
        assert purple().eq(&unwrap(parse_color(~"PURPLE")));
        assert fuchsia().eq(&unwrap(parse_color(~"FUCHSIA")));
        assert olive().eq(&unwrap(parse_color(~"oLiVe")));
        assert yellow().eq(&unwrap(parse_color(~"yellow")));
        assert navy().eq(&unwrap(parse_color(~"NAVY")));
        assert teal().eq(&unwrap(parse_color(~"Teal")));
        assert aqua().eq(&unwrap(parse_color(~"Aqua")));
        assert None == parse_color(~"foobarbaz");
    }

    #[test]
    fn test_parsing_rgb() {
        assert red().eq(&unwrap(parse_color(~"rgb(255,0,0)")));
        assert red().eq(&unwrap(parse_color(~"rgba(255,0,0,1.0)")));
        assert red().eq(&unwrap(parse_color(~"rgba(255,0,0,1)")));
        assert lime().eq(&unwrap(parse_color(~"rgba(0,255,0,1.00)")));
        assert rgb(1u8,2u8,3u8).eq(&unwrap(parse_color(~"rgb(1,2,03)")));
        assert rgba(15u8,250u8,3u8,0.5).eq(&unwrap(parse_color(~"rgba(15,250,3,.5)")));
        assert rgba(15u8,250u8,3u8,0.5).eq(&unwrap(parse_color(~"rgba(15,250,3,0.5)")));
        assert None == parse_color(~"rbga(1,2,3)");
    }

    #[test]
    fn test_parsing_hsl() {
        assert red().eq(&unwrap(parse_color(~"hsl(0,1,.5)")));
        assert lime().eq(&unwrap(parse_color(~"hsl(120.0,1.0,.5)")));
        assert blue().eq(&unwrap(parse_color(~"hsl(240.0,1.0,.5)")));
        assert green().eq(&unwrap(parse_color(~"hsl(120.0,1.0,.25)")));
        assert white().eq(&unwrap(parse_color(~"hsl(1.0,1.,1.0)")));
        assert white().eq(&unwrap(parse_color(~"hsl(129.0,0.3,1.0)")));
        assert black().eq(&unwrap(parse_color(~"hsl(231.2,0.75,0.0)")));
        assert black().eq(&unwrap(parse_color(~"hsl(11.2,0.0,0.0)")));
        assert gray().eq(&unwrap(parse_color(~"hsl(0.0,0.0,0.5)")));
        assert maroon().eq(&unwrap(parse_color(~"hsl(0.0,1.0,0.25)")));
        assert purple().eq(&unwrap(parse_color(~"hsl(300.0,1.0,0.25)")));
        assert fuchsia().eq(&unwrap(parse_color(~"hsl(300,1.0,0.5)")));
        assert olive().eq(&unwrap(parse_color(~"hsl(60.,1.0,0.25)")));
        assert yellow().eq(&unwrap(parse_color(~"hsl(60.,1.0,0.5)")));
        assert navy().eq(&unwrap(parse_color(~"hsl(240.0,1.0,.25)")));
        assert teal().eq(&unwrap(parse_color(~"hsl(180.0,1.0,.25)")));
        assert aqua().eq(&unwrap(parse_color(~"hsl(180.0,1.0,.5)")));
        assert None == parse_color(~"hsl(1,2,3,.4)");
    }
}

/** Define the colors specified by css */
pub mod css_colors {
    // The 16 basic css colors
    pub fn black() -> Color {
        Color {red : 0u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn silver() -> Color {
        Color {red : 192u8, green : 192u8, blue : 192u8, alpha : 1.0}
    }
    pub fn gray() -> Color {
        Color {red : 128u8, green : 128u8, blue : 128u8, alpha : 1.0}
    }
    pub fn white() -> Color {
        Color {red : 255u8, green : 255u8, blue : 255u8, alpha : 1.0}
    }
    pub fn maroon() -> Color {
        Color {red : 128u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn red() -> Color {
        Color {red : 255u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn purple() -> Color {
        Color {red : 128u8, green : 0u8, blue : 128u8, alpha : 1.0}
    }
    pub fn fuchsia() -> Color {
        Color {red : 255u8, green : 0u8, blue : 255u8, alpha : 1.0}
    }
    pub fn green() -> Color {
        Color {red : 0u8, green : 128u8, blue : 0u8, alpha : 1.0}
    }
    pub fn lime() -> Color {
        Color {red : 0u8, green : 255u8, blue : 0u8, alpha : 1.0}
    }
    pub fn olive() -> Color {
        Color {red : 128u8, green : 128u8, blue : 0u8, alpha : 1.0}
    }
    pub fn yellow() -> Color {
        Color {red : 255u8, green : 255u8, blue : 0u8, alpha : 1.0}
    }
    pub fn navy() -> Color {
        Color {red : 0u8, green : 0u8, blue : 128u8, alpha : 1.0}
    }
    pub fn blue() -> Color {
        Color {red : 0u8, green : 0u8, blue : 255u8, alpha : 1.0}
    }
    pub fn teal() -> Color {
        Color {red : 0u8, green : 128u8, blue : 128u8, alpha : 1.0}
    }
    pub fn aqua() -> Color {
        Color {red : 0u8, green : 255u8, blue : 255u8, alpha : 1.0}
    }

    // The other 130 css colors
    // TODO
}
<commit_msg>Update for language changes<commit_after>use float::round;
use libc::types::os::arch::c95::c_double;
use color::css_colors::*;
use cmp::Eq;

pub struct Color { red : u8, green : u8, blue : u8, alpha : float}

impl Color : Eq {
    pure fn eq(&self, other: &Color) -> bool {
        return self.red == other.red && self.green == other.green && self.blue == other.blue &&
               self.alpha == other.alpha;
    }
    pure fn ne(&self, other: &Color) -> bool {
        !self.eq(other)
    }
}

pub fn rgba(r : u8, g : u8, b : u8, a : float) -> Color {
    Color { red : r, green : g, blue : b, alpha : a}
}

pub fn rgb(r : u8, g : u8, b : u8) -> Color {
    return rgba(r, g, b, 1.0);
}

pub fn hsla(h : float, s : float, l : float, a : float) -> Color {
    // Algorithm for converting hsl to rbg taken from
    // http://www.w3.org/TR/2003/CR-css3-color-20030514/#hsl-color
    let m2 = if l <= 0.5 { l*(s + 1.0) } else { l + s - l*s };
    let m1 = l*2.0 - m2;
    let h = h / 360.0;

    fn hue_to_rgb(m1 : float, m2 : float, h : float) -> float {
        let h = if h < 0.0 { h + 1.0 } else if h > 1.0 { h - 1.0 } else { h };

        match h {
          0.0 .. 1.0/6.0 => m1 + (m2 - m1)*h*6.0,
          1.0/6.0 .. 1.0/2.0 => m2,
          1.0/2.0 .. 2.0/3.0 => m1 + (m2 - m1)*(4.0 - 6.0*h),
          2.0/3.0 .. 1.0 => return m1,
          _ => fail ~"unexpected hue value"
        }
    }

    let r = round(255.0*hue_to_rgb(m1, m2, h + 1.0/3.0) as c_double);;
    let g = round(255.0*hue_to_rgb(m1, m2, h) as c_double);
    let b = round(255.0*hue_to_rgb(m1, m2, h - 1.0/3.0) as c_double);

    return rgba(r as u8, g as u8, b as u8, a);
}

pub fn hsl(h : float, s : float, l : float) -> Color {
    return hsla(h, s, l, 1.0);
}

impl Color {
    fn print() -> ~str {
        fmt!("rgba(%u,%u,%u,%f)", self.red as uint, self.green as uint,
             self.blue as uint, self.alpha)
    }
}

pub mod parsing {
    export parse_color;

    fn fail_unrecognized(col : &str) -> Option<Color> {
        warn!("Unrecognized color %s", col);
        return None;
    }

    /** Match an exact color keyword. */
    fn parse_by_name(color : &str) -> Option<Color> {
        let col = match color.to_lower() {
          ~"black" => black(),
          ~"silver" => silver(),
          ~"gray" => gray(),
          ~"grey" => gray(),
          ~"white" => white(),
          ~"maroon" => maroon(),
          ~"red" => red(),
          ~"purple" => purple(),
          ~"fuchsia" => fuchsia(),
          ~"green" => green(),
          ~"lime" => lime(),
          ~"olive" => olive(),
          ~"yellow" => yellow(),
          ~"navy" => navy(),
          ~"blue" => blue(),
          ~"teal" => teal(),
          ~"aqua" => aqua(),
          _ => return fail_unrecognized(color)
        };

        return Some(col);
    }

    /** Parses a color specification in the form rgb(foo,bar,baz) */
    fn parse_rgb(color : &str) -> Option<Color> {
        // Shave off the rgb( and the )
        let only_colors = color.substr(4u, color.len() - 5u);

        // split up r, g, and b
        let cols = only_colors.split_char(',');
        if cols.len() != 3u { return fail_unrecognized(color); }

        match (u8::from_str(cols[0]), u8::from_str(cols[1]),
             u8::from_str(cols[2])) {
          (Some(r), Some(g), Some(b)) => { Some(rgb(r, g, b)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form rgba(foo,bar,baz,qux) */
    fn parse_rgba(color : &str) -> Option<Color> {
        // Shave off the rgba( and the )
        let only_vals = color.substr(5u, color.len() - 6u);

        // split up r, g, and b
        let cols = only_vals.split_char(',');
        if cols.len() != 4u { return fail_unrecognized(color); }

        match (u8::from_str(cols[0]), u8::from_str(cols[1]),
             u8::from_str(cols[2]), float::from_str(cols[3])) {
          (Some(r), Some(g), Some(b), Some(a)) => { Some(rgba(r, g, b, a)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form hsl(foo,bar,baz) */
    fn parse_hsl(color : &str) -> Option<Color> {
        // Shave off the hsl( and the )
        let only_vals = color.substr(4u, color.len() - 5u);

        // split up h, s, and l
        let vals = only_vals.split_char(',');
        if vals.len() != 3u { return fail_unrecognized(color); }

        match (float::from_str(vals[0]), float::from_str(vals[1]),
             float::from_str(vals[2])) {
          (Some(h), Some(s), Some(l)) => { Some(hsl(h, s, l)) }
          _ => { fail_unrecognized(color) }
        }
    }

    /** Parses a color specification in the form hsla(foo,bar,baz,qux) */
    fn parse_hsla(color : &str) -> Option<Color> {
        // Shave off the hsla( and the )
        let only_vals = color.substr(5u, color.len() - 6u);

        let vals = only_vals.split_char(',');
        if vals.len() != 4u { return fail_unrecognized(color); }

        match (float::from_str(vals[0]), float::from_str(vals[1]),
             float::from_str(vals[2]), float::from_str(vals[3])) {
          (Some(h), Some(s), Some(l), Some(a)) => { Some(hsla(h, s, l, a)) }
          _ => { fail_unrecognized(color) }
        }
    }

    // Currently colors are supported in rgb(a,b,c) form and also by
    // keywords for several common colors.
    // TODO: extend this
    fn parse_color(color : &str) -> Option<Color> {
        match color {
          c if c.starts_with("rgb(") => parse_rgb(c),
          c if c.starts_with("rgba(") => parse_rgba(c),
          c if c.starts_with("hsl(") => parse_hsl(c),
          c if c.starts_with("hsla(") => parse_hsla(c),
          c => parse_by_name(c)
        }
    }
}

#[cfg(test)]
mod test {
    use css_colors::*;
    use option::unwrap;
    use parsing::parse_color;

    #[test]
    fn test_parse_by_name() {
        assert red().eq(&unwrap(parse_color(~"red")));
        assert lime().eq(&unwrap(parse_color(~"Lime")));
        assert blue().eq(&unwrap(parse_color(~"BLUE")));
        assert green().eq(&unwrap(parse_color(~"GreEN")));
        assert white().eq(&unwrap(parse_color(~"white")));
        assert black().eq(&unwrap(parse_color(~"Black")));
        assert gray().eq(&unwrap(parse_color(~"Gray")));
        assert silver().eq(&unwrap(parse_color(~"SiLvEr")));
        assert maroon().eq(&unwrap(parse_color(~"maroon")));
        assert purple().eq(&unwrap(parse_color(~"PURPLE")));
        assert fuchsia().eq(&unwrap(parse_color(~"FUCHSIA")));
        assert olive().eq(&unwrap(parse_color(~"oLiVe")));
        assert yellow().eq(&unwrap(parse_color(~"yellow")));
        assert navy().eq(&unwrap(parse_color(~"NAVY")));
        assert teal().eq(&unwrap(parse_color(~"Teal")));
        assert aqua().eq(&unwrap(parse_color(~"Aqua")));
        assert None == parse_color(~"foobarbaz");
    }

    #[test]
    fn test_parsing_rgb() {
        assert red().eq(&unwrap(parse_color(~"rgb(255,0,0)")));
        assert red().eq(&unwrap(parse_color(~"rgba(255,0,0,1.0)")));
        assert red().eq(&unwrap(parse_color(~"rgba(255,0,0,1)")));
        assert lime().eq(&unwrap(parse_color(~"rgba(0,255,0,1.00)")));
        assert rgb(1u8,2u8,3u8).eq(&unwrap(parse_color(~"rgb(1,2,03)")));
        assert rgba(15u8,250u8,3u8,0.5).eq(&unwrap(parse_color(~"rgba(15,250,3,.5)")));
        assert rgba(15u8,250u8,3u8,0.5).eq(&unwrap(parse_color(~"rgba(15,250,3,0.5)")));
        assert None == parse_color(~"rbga(1,2,3)");
    }

    #[test]
    fn test_parsing_hsl() {
        assert red().eq(&unwrap(parse_color(~"hsl(0,1,.5)")));
        assert lime().eq(&unwrap(parse_color(~"hsl(120.0,1.0,.5)")));
        assert blue().eq(&unwrap(parse_color(~"hsl(240.0,1.0,.5)")));
        assert green().eq(&unwrap(parse_color(~"hsl(120.0,1.0,.25)")));
        assert white().eq(&unwrap(parse_color(~"hsl(1.0,1.,1.0)")));
        assert white().eq(&unwrap(parse_color(~"hsl(129.0,0.3,1.0)")));
        assert black().eq(&unwrap(parse_color(~"hsl(231.2,0.75,0.0)")));
        assert black().eq(&unwrap(parse_color(~"hsl(11.2,0.0,0.0)")));
        assert gray().eq(&unwrap(parse_color(~"hsl(0.0,0.0,0.5)")));
        assert maroon().eq(&unwrap(parse_color(~"hsl(0.0,1.0,0.25)")));
        assert purple().eq(&unwrap(parse_color(~"hsl(300.0,1.0,0.25)")));
        assert fuchsia().eq(&unwrap(parse_color(~"hsl(300,1.0,0.5)")));
        assert olive().eq(&unwrap(parse_color(~"hsl(60.,1.0,0.25)")));
        assert yellow().eq(&unwrap(parse_color(~"hsl(60.,1.0,0.5)")));
        assert navy().eq(&unwrap(parse_color(~"hsl(240.0,1.0,.25)")));
        assert teal().eq(&unwrap(parse_color(~"hsl(180.0,1.0,.25)")));
        assert aqua().eq(&unwrap(parse_color(~"hsl(180.0,1.0,.5)")));
        assert None == parse_color(~"hsl(1,2,3,.4)");
    }
}

/** Define the colors specified by css */
pub mod css_colors {
    // The 16 basic css colors
    pub fn black() -> Color {
        Color {red : 0u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn silver() -> Color {
        Color {red : 192u8, green : 192u8, blue : 192u8, alpha : 1.0}
    }
    pub fn gray() -> Color {
        Color {red : 128u8, green : 128u8, blue : 128u8, alpha : 1.0}
    }
    pub fn white() -> Color {
        Color {red : 255u8, green : 255u8, blue : 255u8, alpha : 1.0}
    }
    pub fn maroon() -> Color {
        Color {red : 128u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn red() -> Color {
        Color {red : 255u8, green : 0u8, blue : 0u8, alpha : 1.0}
    }
    pub fn purple() -> Color {
        Color {red : 128u8, green : 0u8, blue : 128u8, alpha : 1.0}
    }
    pub fn fuchsia() -> Color {
        Color {red : 255u8, green : 0u8, blue : 255u8, alpha : 1.0}
    }
    pub fn green() -> Color {
        Color {red : 0u8, green : 128u8, blue : 0u8, alpha : 1.0}
    }
    pub fn lime() -> Color {
        Color {red : 0u8, green : 255u8, blue : 0u8, alpha : 1.0}
    }
    pub fn olive() -> Color {
        Color {red : 128u8, green : 128u8, blue : 0u8, alpha : 1.0}
    }
    pub fn yellow() -> Color {
        Color {red : 255u8, green : 255u8, blue : 0u8, alpha : 1.0}
    }
    pub fn navy() -> Color {
        Color {red : 0u8, green : 0u8, blue : 128u8, alpha : 1.0}
    }
    pub fn blue() -> Color {
        Color {red : 0u8, green : 0u8, blue : 255u8, alpha : 1.0}
    }
    pub fn teal() -> Color {
        Color {red : 0u8, green : 128u8, blue : 128u8, alpha : 1.0}
    }
    pub fn aqua() -> Color {
        Color {red : 0u8, green : 255u8, blue : 255u8, alpha : 1.0}
    }

    // The other 130 css colors
    // TODO
}
<|endoftext|>


<commit_before>use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events_macros::{event_enum, EventEnumFromEvent};
use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId};
use serde::{de, Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;

use crate::{
    from_raw_json_value, room::redaction::SyncRoomRedactionEvent, Redact, UnsignedDeHelper,
};

event_enum! {
    /// Any global account data event.
    enum GlobalAccountData {
        "m.direct",
        "m.ignored_user_list",
        "m.push_rules",
    }

    /// Any room account data event.
    enum RoomAccountData {
        "m.fully_read",
        "m.tag",
    }

    /// Any ephemeral room event.
    enum EphemeralRoom {
        "m.receipt",
        "m.typing",
    }

    /// Any message event.
    enum Message {
        "m.call.answer",
        "m.call.invite",
        "m.call.hangup",
        "m.call.candidates",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.start",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.cancel",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.accept",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.key",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.reaction",
        "m.room.encrypted",
        "m.room.message",
        "m.room.message.feedback",
        "m.room.redaction",
        "m.sticker",
    }

    /// Any state event.
    enum State {
        "m.policy.rule.room",
        "m.policy.rule.server",
        "m.policy.rule.user",
        "m.room.aliases",
        "m.room.avatar",
        "m.room.canonical_alias",
        "m.room.create",
        "m.room.encryption",
        "m.room.guest_access",
        "m.room.history_visibility",
        "m.room.join_rules",
        "m.room.member",
        "m.room.name",
        "m.room.pinned_events",
        "m.room.power_levels",
        "m.room.server_acl",
        "m.room.third_party_invite",
        "m.room.tombstone",
        "m.room.topic",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.space.child",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.space.parent",
    }

    /// Any to-device event.
    enum ToDevice {
        "m.dummy",
        "m.room_key",
        "m.room_key_request",
        "m.forwarded_room_key",
        "m.key.verification.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        "m.key.verification.start",
        "m.key.verification.cancel",
        "m.key.verification.accept",
        "m.key.verification.key",
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        "m.room.encrypted",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.secret.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.secret.send",
    }
}

/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around <https://github.com/rust-lang/rust/issues/52607>.
macro_rules! doc_concat {
    ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* );
}

macro_rules! room_ev_accessor {
    ($field:ident: $ty:ty) => {
        doc_concat! {
            #[doc = concat!("Returns this event's `", stringify!($field), "` field.")]
            pub fn $field(&self) -> $ty {
                match self {
                    Self::Message(ev) => ev.$field(),
                    Self::State(ev) => ev.$field(),
                    Self::RedactedMessage(ev) => ev.$field(),
                    Self::RedactedState(ev) => ev.$field(),
                }
            }
        }
    };
}

/// Any room event.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, Serialize, EventEnumFromEvent)]
#[serde(untagged)]
pub enum AnyRoomEvent {
    /// Any message event.
    Message(AnyMessageEvent),

    /// Any state event.
    State(AnyStateEvent),

    /// Any message event that has been redacted.
    RedactedMessage(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    RedactedState(AnyRedactedStateEvent),
}

impl AnyRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(room_id: &RoomId);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);
}

/// Any sync room event.
///
/// Sync room events are room event without a `room_id`, as returned in `/sync` responses.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, Serialize, EventEnumFromEvent)]
#[serde(untagged)]
pub enum AnySyncRoomEvent {
    /// Any sync message event.
    Message(AnySyncMessageEvent),

    /// Any sync state event.
    State(AnySyncStateEvent),

    /// Any sync message event that has been redacted.
    RedactedMessage(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    RedactedState(AnyRedactedSyncStateEvent),
}

impl AnySyncRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);

    /// Converts `self` to an `AnyRoomEvent` by adding the given a room ID.
    pub fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent {
        match self {
            Self::Message(ev) => AnyRoomEvent::Message(ev.into_full_event(room_id)),
            Self::State(ev) => AnyRoomEvent::State(ev.into_full_event(room_id)),
            Self::RedactedMessage(ev) => AnyRoomEvent::RedactedMessage(ev.into_full_event(room_id)),
            Self::RedactedState(ev) => AnyRoomEvent::RedactedState(ev.into_full_event(room_id)),
        }
    }
}

#[derive(Deserialize)]
#[allow(clippy::exhaustive_structs)]
struct EventDeHelper {
    pub state_key: Option<de::IgnoredAny>,
    pub unsigned: Option<UnsignedDeHelper>,
}

impl<'de> Deserialize<'de> for AnyRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

impl<'de> Deserialize<'de> for AnySyncRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

/// Any redacted room event.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, EventEnumFromEvent)]
pub enum AnyRedactedRoomEvent {
    /// Any message event that has been redacted.
    Message(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    State(AnyRedactedStateEvent),
}

impl Redact for AnyRoomEvent {
    type Redacted = AnyRedactedRoomEvent;

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted {
        match self {
            Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)),
            Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)),
            Self::RedactedMessage(ev) => Self::Redacted::Message(ev),
            Self::RedactedState(ev) => Self::Redacted::State(ev),
        }
    }
}

impl From<AnyRedactedRoomEvent> for AnyRoomEvent {
    fn from(ev: AnyRedactedRoomEvent) -> Self {
        match ev {
            AnyRedactedRoomEvent::Message(ev) => Self::RedactedMessage(ev),
            AnyRedactedRoomEvent::State(ev) => Self::RedactedState(ev),
        }
    }
}

/// Any redacted sync room event (room event without a `room_id`, as returned in `/sync` responses)
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, EventEnumFromEvent)]
pub enum AnyRedactedSyncRoomEvent {
    /// Any sync message event that has been redacted.
    Message(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    State(AnyRedactedSyncStateEvent),
}

impl Redact for AnySyncRoomEvent {
    type Redacted = AnyRedactedSyncRoomEvent;

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted {
        match self {
            Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)),
            Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)),
            Self::RedactedMessage(ev) => Self::Redacted::Message(ev),
            Self::RedactedState(ev) => Self::Redacted::State(ev),
        }
    }
}

impl From<AnyRedactedSyncRoomEvent> for AnySyncRoomEvent {
    fn from(ev: AnyRedactedSyncRoomEvent) -> Self {
        match ev {
            AnyRedactedSyncRoomEvent::Message(ev) => Self::RedactedMessage(ev),
            AnyRedactedSyncRoomEvent::State(ev) => Self::RedactedState(ev),
        }
    }
}

impl AnyMessageEventContent {
    /// Get a copy of the event's `m.relates_to` field, if any.
    ///
    /// This is a helper function intended for encryption. There should not be a reason to access
    /// `m.relates_to` without first destructuring an `AnyMessageEventContent` otherwise.
    pub fn relation(&self) -> Option<crate::room::encrypted::Relation> {
        #[cfg(feature = "unstable-pre-spec")]
        use crate::key::verification::{
            accept::KeyVerificationAcceptEventContent, cancel::KeyVerificationCancelEventContent,
            done::KeyVerificationDoneEventContent, key::KeyVerificationKeyEventContent,
            mac::KeyVerificationMacEventContent, ready::KeyVerificationReadyEventContent,
            start::KeyVerificationStartEventContent,
        };

        match self {
            #[cfg(feature = "unstable-pre-spec")]
            #[rustfmt::skip]
            AnyMessageEventContent::KeyVerificationReady(KeyVerificationReadyEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationStart(KeyVerificationStartEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationCancel(KeyVerificationCancelEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationAccept(KeyVerificationAcceptEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationKey(KeyVerificationKeyEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationMac(KeyVerificationMacEventContent { relates_to, .. })
            | AnyMessageEventContent::KeyVerificationDone(KeyVerificationDoneEventContent { relates_to, .. }) => {
                Some(relates_to.clone().into())
            },
            #[cfg(feature = "unstable-pre-spec")]
            AnyMessageEventContent::Reaction(ev) => Some(ev.relates_to.clone().into()),
            AnyMessageEventContent::RoomEncrypted(ev) => ev.relates_to.clone(),
            AnyMessageEventContent::RoomMessage(ev) => ev.relates_to.clone().map(Into::into),
            AnyMessageEventContent::CallAnswer(_)
            | AnyMessageEventContent::CallInvite(_)
            | AnyMessageEventContent::CallHangup(_)
            | AnyMessageEventContent::CallCandidates(_)
            | AnyMessageEventContent::RoomMessageFeedback(_)
            | AnyMessageEventContent::RoomRedaction(_)
            | AnyMessageEventContent::Sticker(_)
            | AnyMessageEventContent::_Custom { .. } => None,
        }
    }
}
<commit_msg>events: Use Self alias in AnyMessageEventContent::relation<commit_after>use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events_macros::{event_enum, EventEnumFromEvent};
use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId};
use serde::{de, Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;

use crate::{
    from_raw_json_value, room::redaction::SyncRoomRedactionEvent, Redact, UnsignedDeHelper,
};

event_enum! {
    /// Any global account data event.
    enum GlobalAccountData {
        "m.direct",
        "m.ignored_user_list",
        "m.push_rules",
    }

    /// Any room account data event.
    enum RoomAccountData {
        "m.fully_read",
        "m.tag",
    }

    /// Any ephemeral room event.
    enum EphemeralRoom {
        "m.receipt",
        "m.typing",
    }

    /// Any message event.
    enum Message {
        "m.call.answer",
        "m.call.invite",
        "m.call.hangup",
        "m.call.candidates",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.start",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.cancel",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.accept",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.key",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.reaction",
        "m.room.encrypted",
        "m.room.message",
        "m.room.message.feedback",
        "m.room.redaction",
        "m.sticker",
    }

    /// Any state event.
    enum State {
        "m.policy.rule.room",
        "m.policy.rule.server",
        "m.policy.rule.user",
        "m.room.aliases",
        "m.room.avatar",
        "m.room.canonical_alias",
        "m.room.create",
        "m.room.encryption",
        "m.room.guest_access",
        "m.room.history_visibility",
        "m.room.join_rules",
        "m.room.member",
        "m.room.name",
        "m.room.pinned_events",
        "m.room.power_levels",
        "m.room.server_acl",
        "m.room.third_party_invite",
        "m.room.tombstone",
        "m.room.topic",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.space.child",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.space.parent",
    }

    /// Any to-device event.
    enum ToDevice {
        "m.dummy",
        "m.room_key",
        "m.room_key_request",
        "m.forwarded_room_key",
        "m.key.verification.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        "m.key.verification.start",
        "m.key.verification.cancel",
        "m.key.verification.accept",
        "m.key.verification.key",
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        "m.room.encrypted",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.secret.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.secret.send",
    }
}

/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around <https://github.com/rust-lang/rust/issues/52607>.
macro_rules! doc_concat {
    ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* );
}

macro_rules! room_ev_accessor {
    ($field:ident: $ty:ty) => {
        doc_concat! {
            #[doc = concat!("Returns this event's `", stringify!($field), "` field.")]
            pub fn $field(&self) -> $ty {
                match self {
                    Self::Message(ev) => ev.$field(),
                    Self::State(ev) => ev.$field(),
                    Self::RedactedMessage(ev) => ev.$field(),
                    Self::RedactedState(ev) => ev.$field(),
                }
            }
        }
    };
}

/// Any room event.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, Serialize, EventEnumFromEvent)]
#[serde(untagged)]
pub enum AnyRoomEvent {
    /// Any message event.
    Message(AnyMessageEvent),

    /// Any state event.
    State(AnyStateEvent),

    /// Any message event that has been redacted.
    RedactedMessage(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    RedactedState(AnyRedactedStateEvent),
}

impl AnyRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(room_id: &RoomId);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);
}

/// Any sync room event.
///
/// Sync room events are room event without a `room_id`, as returned in `/sync` responses.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, Serialize, EventEnumFromEvent)]
#[serde(untagged)]
pub enum AnySyncRoomEvent {
    /// Any sync message event.
    Message(AnySyncMessageEvent),

    /// Any sync state event.
    State(AnySyncStateEvent),

    /// Any sync message event that has been redacted.
    RedactedMessage(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    RedactedState(AnyRedactedSyncStateEvent),
}

impl AnySyncRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);

    /// Converts `self` to an `AnyRoomEvent` by adding the given a room ID.
    pub fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent {
        match self {
            Self::Message(ev) => AnyRoomEvent::Message(ev.into_full_event(room_id)),
            Self::State(ev) => AnyRoomEvent::State(ev.into_full_event(room_id)),
            Self::RedactedMessage(ev) => AnyRoomEvent::RedactedMessage(ev.into_full_event(room_id)),
            Self::RedactedState(ev) => AnyRoomEvent::RedactedState(ev.into_full_event(room_id)),
        }
    }
}

#[derive(Deserialize)]
#[allow(clippy::exhaustive_structs)]
struct EventDeHelper {
    pub state_key: Option<de::IgnoredAny>,
    pub unsigned: Option<UnsignedDeHelper>,
}

impl<'de> Deserialize<'de> for AnyRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

impl<'de> Deserialize<'de> for AnySyncRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

/// Any redacted room event.
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, EventEnumFromEvent)]
pub enum AnyRedactedRoomEvent {
    /// Any message event that has been redacted.
    Message(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    State(AnyRedactedStateEvent),
}

impl Redact for AnyRoomEvent {
    type Redacted = AnyRedactedRoomEvent;

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted {
        match self {
            Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)),
            Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)),
            Self::RedactedMessage(ev) => Self::Redacted::Message(ev),
            Self::RedactedState(ev) => Self::Redacted::State(ev),
        }
    }
}

impl From<AnyRedactedRoomEvent> for AnyRoomEvent {
    fn from(ev: AnyRedactedRoomEvent) -> Self {
        match ev {
            AnyRedactedRoomEvent::Message(ev) => Self::RedactedMessage(ev),
            AnyRedactedRoomEvent::State(ev) => Self::RedactedState(ev),
        }
    }
}

/// Any redacted sync room event (room event without a `room_id`, as returned in `/sync` responses)
#[allow(clippy::large_enum_variant, clippy::exhaustive_enums)]
#[derive(Clone, Debug, EventEnumFromEvent)]
pub enum AnyRedactedSyncRoomEvent {
    /// Any sync message event that has been redacted.
    Message(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    State(AnyRedactedSyncStateEvent),
}

impl Redact for AnySyncRoomEvent {
    type Redacted = AnyRedactedSyncRoomEvent;

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted {
        match self {
            Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)),
            Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)),
            Self::RedactedMessage(ev) => Self::Redacted::Message(ev),
            Self::RedactedState(ev) => Self::Redacted::State(ev),
        }
    }
}

impl From<AnyRedactedSyncRoomEvent> for AnySyncRoomEvent {
    fn from(ev: AnyRedactedSyncRoomEvent) -> Self {
        match ev {
            AnyRedactedSyncRoomEvent::Message(ev) => Self::RedactedMessage(ev),
            AnyRedactedSyncRoomEvent::State(ev) => Self::RedactedState(ev),
        }
    }
}

impl AnyMessageEventContent {
    /// Get a copy of the event's `m.relates_to` field, if any.
    ///
    /// This is a helper function intended for encryption. There should not be a reason to access
    /// `m.relates_to` without first destructuring an `AnyMessageEventContent` otherwise.
    pub fn relation(&self) -> Option<crate::room::encrypted::Relation> {
        #[cfg(feature = "unstable-pre-spec")]
        use crate::key::verification::{
            accept::KeyVerificationAcceptEventContent, cancel::KeyVerificationCancelEventContent,
            done::KeyVerificationDoneEventContent, key::KeyVerificationKeyEventContent,
            mac::KeyVerificationMacEventContent, ready::KeyVerificationReadyEventContent,
            start::KeyVerificationStartEventContent,
        };

        match self {
            #[cfg(feature = "unstable-pre-spec")]
            #[rustfmt::skip]
            Self::KeyVerificationReady(KeyVerificationReadyEventContent { relates_to, .. })
            | Self::KeyVerificationStart(KeyVerificationStartEventContent { relates_to, .. })
            | Self::KeyVerificationCancel(KeyVerificationCancelEventContent { relates_to, .. })
            | Self::KeyVerificationAccept(KeyVerificationAcceptEventContent { relates_to, .. })
            | Self::KeyVerificationKey(KeyVerificationKeyEventContent { relates_to, .. })
            | Self::KeyVerificationMac(KeyVerificationMacEventContent { relates_to, .. })
            | Self::KeyVerificationDone(KeyVerificationDoneEventContent { relates_to, .. }) => {
                Some(relates_to.clone().into())
            },
            #[cfg(feature = "unstable-pre-spec")]
            Self::Reaction(ev) => Some(ev.relates_to.clone().into()),
            Self::RoomEncrypted(ev) => ev.relates_to.clone(),
            Self::RoomMessage(ev) => ev.relates_to.clone().map(Into::into),
            Self::CallAnswer(_)
            | Self::CallInvite(_)
            | Self::CallHangup(_)
            | Self::CallCandidates(_)
            | Self::RoomMessageFeedback(_)
            | Self::RoomRedaction(_)
            | Self::Sticker(_)
            | Self::_Custom { .. } => None,
        }
    }
}
<|endoftext|>