<commit_before>extern crate errno;

extern crate libc;

use super::constants::*;
use super::ffi::*;

use std::ffi::CString;
use std::os::unix::raw::{gid_t, uid_t};
use std::ptr;
use std::result;
use std::str;

pub type Error = errno::Errno;
pub type Result<T> = result::Result<T, Error>;

fn check_call<T>(res: libc::c_long, value: T) -> Result<T> {
    if res == -1 {
        Err(errno::errno())
    } else {
        Ok(value)
    }
}

fn check_call_ret(res: libc::c_long) -> Result<libc::c_long> {
    if res == -1 {
        Err(errno::errno())
    } else {
        Ok(res)
    }
}

fn get_keyring(id: KeyringSerial, create: bool) -> Result<Keyring> {
    let res = unsafe { keyctl_get_keyring_ID(id, create as libc::c_int) };
    check_call(res as libc::c_long, Keyring { id: res, })
}

pub struct Keyring {
    id: KeyringSerial,
}

impl Keyring {
    pub fn set_default(keyring: DefaultKeyring) -> Result<()> {
        check_call(unsafe { keyctl_set_reqkey_keyring(keyring.serial()) }, ())
    }

    pub fn export_session_keyring_to_parent() -> Result<()> {
        check_call(unsafe { keyctl_session_to_parent() }, ())
    }

    pub fn attach(id: KeyringSerial) -> Result<Self> {
        get_keyring(id, false)
    }

    pub fn attach_or_create(id: KeyringSerial) -> Result<Self> {
        get_keyring(id, true)
    }

    pub fn join_anonymous_session() -> Result<Self> {
        let res = unsafe { keyctl_join_session_keyring(ptr::null()) };
        check_call(res as libc::c_long, Keyring { id: res })
    }

    pub fn join_session(name: &str) -> Result<Self> {
        let nameptr = CString::new(name).unwrap().as_ptr();
        let res = unsafe { keyctl_join_session_keyring(nameptr) };
        check_call(res as libc::c_long, Keyring { id: res })
    }

    pub fn clear(&mut self) -> Result<()> {
        check_call(unsafe { keyctl_clear(self.id) }, ())
    }

    pub fn link(&mut self, key: &Key) -> Result<()> {
        check_call(unsafe { keyctl_link(key.id, self.id) }, ())
    }

    pub fn unlink(&mut self, key: &Key) -> Result<()> {
        check_call(unsafe { keyctl_unlink(key.id, self.id) }, ())
    }

    pub fn search(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { keyctl_search(self.id, typeptr, descptr, self.id) };
        check_call(res, Key { id: res as key_serial_t, })
    }

    pub fn read(&self) -> Result<Vec<Key>> {
        unimplemented!()
    }

    pub fn attach_persistent(&mut self) -> Result<Keyring> {
        let res = unsafe { keyctl_get_persistent(-1, self.id) };
        check_call(res, Keyring { id: res as key_serial_t, })
    }

    pub fn add_key(&mut self, type_: &str, description: &str, payload: &[u8]) -> Result<Key> {
        if type_ == "keyring" {
            return Err(errno::Errno(libc::EINVAL));
        }

        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { add_key(typeptr, descptr, payload.as_ptr() as *const libc::c_void, payload.len(), self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn add_keyring(&mut self) -> Result<Keyring> {
        let typeptr = CString::new("keyring").unwrap().as_ptr();
        let res = unsafe { add_key(typeptr, ptr::null(), ptr::null(), 0, self.id) };
        check_call(res as libc::c_long, Keyring { id: res, })
    }

    pub fn find_key(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { find_key_by_type_and_desc(typeptr, descptr, self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn request_key(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { request_key(typeptr, descptr, ptr::null(), self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn request_key_with_fallback(&mut self, type_: &str, description: &str, info: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let infoptr = CString::new(info).unwrap().as_ptr();
        let res = unsafe { request_key(typeptr, descptr, infoptr, self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }
}

pub struct Key {
    id: KeyringSerial,
}

impl Key {
    pub fn request(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.request_key(type_, description)
    }

    pub fn request_with_fallback(type_: &str, description: &str, info: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.request_key_with_fallback(type_, description, info)
    }

    pub fn find(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.find_key(type_, description)
    }

    pub fn search(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.search(type_, description)
    }

    pub fn keyring(&self) -> Result<Keyring> {
        get_keyring(self.id, false)
    }

    pub fn update(&mut self, data: &[u8]) -> Result<()> {
        check_call(unsafe { keyctl_update(self.id, data.as_ptr() as *const libc::c_void, data.len()) }, ())
    }

    pub fn revoke(self) -> Result<()> {
        check_call(unsafe { keyctl_revoke(self.id) }, ())
    }

    pub fn chown(&mut self, uid: uid_t) -> Result<()> {
        check_call(unsafe { keyctl_chown(self.id, uid, -1) }, ())
    }

    pub fn chgrp(&mut self, gid: gid_t) -> Result<()> {
        check_call(unsafe { keyctl_chown(self.id, -1, gid) }, ())
    }

    pub fn set_permissions(&mut self, perms: KeyPermissions) -> Result<()> {
        check_call(unsafe { keyctl_setperm(self.id, perms) }, ())
    }

    pub fn description(&self) -> Result<String> {
        let sz = try!(check_call_ret(unsafe { keyctl_describe(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_describe(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        let str_slice = str::from_utf8(&buffer[..]).unwrap();
        Ok(str_slice.to_owned())
    }

    pub fn read(&self) -> Result<Vec<u8>> {
        let sz = try!(check_call_ret(unsafe { keyctl_read(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_read(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        Ok(buffer)
    }

    pub fn set_timeout(&mut self, timeout: u32) -> Result<()> {
        check_call(unsafe { keyctl_set_timeout(self.id, timeout) }, ())
    }

    pub fn get_security(&self) -> Result<String> {
        let sz = try!(check_call_ret(unsafe { keyctl_get_security(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_get_security(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        let str_slice = str::from_utf8(&buffer[..]).unwrap();
        Ok(str_slice.to_owned())
    }

    pub fn invalidate(self) -> Result<()> {
        check_call(unsafe { keyctl_invalidate(self.id) }, ())
    }
}
<commit_msg>api: add a structured description method<commit_after>extern crate errno;

extern crate libc;

use super::constants::*;
use super::ffi::*;

use std::ffi::CString;
use std::os::unix::raw::{gid_t, uid_t};
use std::ptr;
use std::result;
use std::str;

pub type Error = errno::Errno;
pub type Result<T> = result::Result<T, Error>;

fn check_call<T>(res: libc::c_long, value: T) -> Result<T> {
    if res == -1 {
        Err(errno::errno())
    } else {
        Ok(value)
    }
}

fn check_call_ret(res: libc::c_long) -> Result<libc::c_long> {
    if res == -1 {
        Err(errno::errno())
    } else {
        Ok(res)
    }
}

fn get_keyring(id: KeyringSerial, create: bool) -> Result<Keyring> {
    let res = unsafe { keyctl_get_keyring_ID(id, create as libc::c_int) };
    check_call(res as libc::c_long, Keyring { id: res, })
}

pub struct Keyring {
    id: KeyringSerial,
}

impl Keyring {
    pub fn set_default(keyring: DefaultKeyring) -> Result<()> {
        check_call(unsafe { keyctl_set_reqkey_keyring(keyring.serial()) }, ())
    }

    pub fn export_session_keyring_to_parent() -> Result<()> {
        check_call(unsafe { keyctl_session_to_parent() }, ())
    }

    pub fn attach(id: KeyringSerial) -> Result<Self> {
        get_keyring(id, false)
    }

    pub fn attach_or_create(id: KeyringSerial) -> Result<Self> {
        get_keyring(id, true)
    }

    pub fn join_anonymous_session() -> Result<Self> {
        let res = unsafe { keyctl_join_session_keyring(ptr::null()) };
        check_call(res as libc::c_long, Keyring { id: res })
    }

    pub fn join_session(name: &str) -> Result<Self> {
        let nameptr = CString::new(name).unwrap().as_ptr();
        let res = unsafe { keyctl_join_session_keyring(nameptr) };
        check_call(res as libc::c_long, Keyring { id: res })
    }

    pub fn clear(&mut self) -> Result<()> {
        check_call(unsafe { keyctl_clear(self.id) }, ())
    }

    pub fn link(&mut self, key: &Key) -> Result<()> {
        check_call(unsafe { keyctl_link(key.id, self.id) }, ())
    }

    pub fn unlink(&mut self, key: &Key) -> Result<()> {
        check_call(unsafe { keyctl_unlink(key.id, self.id) }, ())
    }

    pub fn search(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { keyctl_search(self.id, typeptr, descptr, self.id) };
        check_call(res, Key { id: res as key_serial_t, })
    }

    pub fn read(&self) -> Result<Vec<Key>> {
        unimplemented!()
    }

    pub fn attach_persistent(&mut self) -> Result<Keyring> {
        let res = unsafe { keyctl_get_persistent(-1, self.id) };
        check_call(res, Keyring { id: res as key_serial_t, })
    }

    pub fn add_key(&mut self, type_: &str, description: &str, payload: &[u8]) -> Result<Key> {
        if type_ == "keyring" {
            return Err(errno::Errno(libc::EINVAL));
        }

        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { add_key(typeptr, descptr, payload.as_ptr() as *const libc::c_void, payload.len(), self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn add_keyring(&mut self) -> Result<Keyring> {
        let typeptr = CString::new("keyring").unwrap().as_ptr();
        let res = unsafe { add_key(typeptr, ptr::null(), ptr::null(), 0, self.id) };
        check_call(res as libc::c_long, Keyring { id: res, })
    }

    pub fn find_key(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { find_key_by_type_and_desc(typeptr, descptr, self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn request_key(&mut self, type_: &str, description: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let res = unsafe { request_key(typeptr, descptr, ptr::null(), self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }

    pub fn request_key_with_fallback(&mut self, type_: &str, description: &str, info: &str) -> Result<Key> {
        let typeptr = CString::new(type_).unwrap().as_ptr();
        let descptr = CString::new(description).unwrap().as_ptr();
        let infoptr = CString::new(info).unwrap().as_ptr();
        let res = unsafe { request_key(typeptr, descptr, infoptr, self.id) };
        check_call(res as libc::c_long, Key { id: res, })
    }
}

pub struct Key {
    id: KeyringSerial,
}

impl Key {
    pub fn request(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.request_key(type_, description)
    }

    pub fn request_with_fallback(type_: &str, description: &str, info: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.request_key_with_fallback(type_, description, info)
    }

    pub fn find(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.find_key(type_, description)
    }

    pub fn search(type_: &str, description: &str) -> Result<Key> {
        let mut keyring = Keyring { id: 0, };
        keyring.search(type_, description)
    }

    pub fn keyring(&self) -> Result<Keyring> {
        get_keyring(self.id, false)
    }

    pub fn update(&mut self, data: &[u8]) -> Result<()> {
        check_call(unsafe { keyctl_update(self.id, data.as_ptr() as *const libc::c_void, data.len()) }, ())
    }

    pub fn revoke(self) -> Result<()> {
        check_call(unsafe { keyctl_revoke(self.id) }, ())
    }

    pub fn chown(&mut self, uid: uid_t) -> Result<()> {
        check_call(unsafe { keyctl_chown(self.id, uid, -1) }, ())
    }

    pub fn chgrp(&mut self, gid: gid_t) -> Result<()> {
        check_call(unsafe { keyctl_chown(self.id, -1, gid) }, ())
    }

    pub fn set_permissions(&mut self, perms: KeyPermissions) -> Result<()> {
        check_call(unsafe { keyctl_setperm(self.id, perms) }, ())
    }

    pub fn description(&self) -> Result<KeyDescription> {
        self.description_raw().map(|desc| {
            KeyDescription::parse(desc)
        })
    }

    fn description_raw(&self) -> Result<String> {
        let sz = try!(check_call_ret(unsafe { keyctl_describe(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_describe(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        let str_slice = str::from_utf8(&buffer[..]).unwrap();
        Ok(str_slice.to_owned())
    }

    pub fn read(&self) -> Result<Vec<u8>> {
        let sz = try!(check_call_ret(unsafe { keyctl_read(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_read(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        Ok(buffer)
    }

    pub fn set_timeout(&mut self, timeout: u32) -> Result<()> {
        check_call(unsafe { keyctl_set_timeout(self.id, timeout) }, ())
    }

    pub fn get_security(&self) -> Result<String> {
        let sz = try!(check_call_ret(unsafe { keyctl_get_security(self.id, ptr::null_mut(), 0) }));
        let mut buffer = Vec::with_capacity(sz as usize);
        try!(check_call_ret(unsafe { keyctl_get_security(self.id, buffer.as_mut_ptr() as *mut libc::c_char, sz as usize) }));
        let str_slice = str::from_utf8(&buffer[..]).unwrap();
        Ok(str_slice.to_owned())
    }

    pub fn invalidate(self) -> Result<()> {
        check_call(unsafe { keyctl_invalidate(self.id) }, ())
    }
}

pub struct KeyDescription {
    pub type_:          String,
    pub uid:            uid_t,
    pub gid:            gid_t,
    pub perms:          KeyPermissions,
    pub description:    String,
}

impl KeyDescription {
    fn parse(desc: String) -> KeyDescription {
        unimplemented!()
    }
}
<|endoftext|>


<commit_before>use std::collections::{HashMap, HashSet};

use chrono::{DateTime, Utc, Duration};

use super::proto::message::{CMessage, SMessage, Error};
use super::proto::criterion::{Criterion, ResourceKind, Filter as FTrait};
use super::proto::resource::{Resource, SResourceUpdate};
use super::{CtlMessage, Message};
use util::random_string;

pub struct Processor {
    subs: HashMap<String, HashSet<usize>>,
    filter_subs: HashMap<u64, Filter>,
    resources: HashMap<String, Resource>,
    // Index by resource kind
    kinds: Vec<HashSet<String>>,
    // Index by torrent ID
    torrent_idx: HashMap<String, HashSet<String>>,
    tokens: HashMap<String, BearerToken>,
}

struct Filter {
    kind: ResourceKind,
    criteria: Vec<Criterion>,
    client: usize,
}

struct BearerToken {
    expiration: DateTime<Utc>,
    client: usize,
    serial: u64,
    kind: TransferKind,
}

#[derive(Clone)]
pub enum TransferKind {
    UploadTorrent { size: u64, path: Option<String> },
    UploadFiles { size: u64, path: String },
    DownloadFile { path: String },
}

const EXPIRATION_DUR: i64 = 120;

impl Processor {
    pub fn new() -> Processor {
        Processor {
            subs: HashMap::new(),
            filter_subs: HashMap::new(),
            resources: HashMap::new(),
            tokens: HashMap::new(),
            torrent_idx: HashMap::new(),
            kinds: vec![HashSet::new(); 6],
        }
    }

    pub fn remove_expired_tokens(&mut self) {
        self.tokens.retain(|_, tok| tok.expiration > Utc::now())
    }

    pub fn get_transfer(&mut self, tok: String) -> Option<(usize, u64, TransferKind)> {
        let mut res = None;
        let rem = match self.tokens.get(&tok) {
            Some(bt) => {
                match &bt.kind {
                    s @ &TransferKind::UploadTorrent { .. } => {
                        res = Some((bt.client, bt.serial, s.clone()));
                        false
                    }
                    _ => true,
                }
            }
            None => {
                res = None;
                false
            }
        };
        if rem {
            let tok = self.tokens.remove(&tok).unwrap();
            res = Some((tok.client, tok.serial, tok.kind));
        }
        res
    }

    pub fn handle_client(
        &mut self,
        client: usize,
        msg: CMessage,
    ) -> (Vec<SMessage>, Option<Message>) {
        let mut resp = Vec::new();
        let mut rmsg = None;
        match msg {
            CMessage::GetResources { serial, ids } => {
                let mut resources = Vec::new();
                for id in ids {
                    if let Some(r) = self.resources.get(&id) {
                        resources.push(SResourceUpdate::Resource(r));
                    } else {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
                resp.push(SMessage::UpdateResources { resources });
            }
            CMessage::Subscribe { serial, ids } => {
                let mut resources = Vec::new();
                for id in ids {
                    if let Some(r) = self.resources.get(&id) {
                        resources.push(SResourceUpdate::Resource(r));
                        self.subs.get_mut(&id).map(|s| s.insert(client));
                    } else {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
                resp.push(SMessage::UpdateResources { resources });
            }
            CMessage::Unsubscribe { ids, .. } => {
                for id in ids {
                    self.subs.get_mut(&id).map(|s| s.remove(&client));
                }
            }
            CMessage::UpdateResource { serial, resource } => {
                match self.resources.get(&resource.id) {
                    Some(&Resource::Torrent(_)) => {
                        rmsg = Some(Message::UpdateTorrent(resource));
                    }
                    Some(&Resource::File(ref f)) => {
                        // TODO: Validate other fields(make sure they're not present)
                        if let Some(p) = resource.priority {
                            rmsg = Some(Message::UpdateFile {
                                id: resource.id,
                                torrent_id: f.torrent_id.to_owned(),
                                priority: p,
                            });
                        }
                    }
                    Some(&Resource::Server(_)) => {
                        rmsg = Some(Message::UpdateServer {
                            id: resource.id,
                            throttle_up: resource.throttle_up,
                            throttle_down: resource.throttle_down,
                        });
                    }
                    Some(_) => {
                        resp.push(SMessage::PermissionDenied(Error {
                            serial: Some(serial),
                            reason: format!("Only torrents, server, and files have mutable fields"),
                        }));
                    }
                    None => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", resource.id),
                        }));
                    }
                }
            }
            CMessage::RemoveResource { serial, id } => {
                match self.resources.get(&id) {
                    Some(&Resource::Torrent(_)) => {
                        rmsg = Some(Message::RemoveTorrent(id));
                    }
                    Some(&Resource::Tracker(ref t)) => {
                        rmsg = Some(Message::RemoveTracker {
                            id,
                            torrent_id: t.id.to_owned(),
                        });
                    }
                    Some(&Resource::Peer(ref p)) => {
                        rmsg = Some(Message::RemovePeer {
                            id,
                            torrent_id: p.id.to_owned(),
                        });
                    }
                    Some(_) => {
                        resp.push(SMessage::InvalidResource(Error {
                            serial: Some(serial),
                            reason: format!("Only torrents, trackers, and peers may be removed"),
                        }));
                    }
                    None => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
            }
            CMessage::FilterSubscribe {
                serial,
                kind,
                criteria,
            } => {
                let f = Filter {
                    criteria,
                    kind,
                    client,
                };
                let mut ids = Vec::new();
                for (_, r) in self.resources.iter() {
                    if r.kind() == kind && f.matches(r) {
                        ids.push(r.id());
                    }
                }
                resp.push(SMessage::ResourcesExtant { serial, ids });
                self.filter_subs.insert(serial, f);
            }
            CMessage::FilterUnsubscribe { filter_serial, .. } => {
                self.filter_subs.remove(&filter_serial);
            }

            CMessage::UploadTorrent { serial, size, path } => {
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::UploadTorrent { size, path },
                ));
            }
            CMessage::UploadMagnet { serial, uri, path } => {}
            CMessage::UploadFiles { serial, size, path } => {
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::UploadFiles { size, path },
                ));
            }
            CMessage::DownloadFile { serial, id } => {
                let path = match self.resources.get(&id) {
                    Some(&Resource::File(ref f)) => f.path.clone(),
                    _ => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown file id {}", id),
                        }));
                        return (resp, rmsg);
                    }
                };
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::DownloadFile { path },
                ));
            }
        }
        (resp, rmsg)
    }

    pub fn handle_ctl(&mut self, msg: CtlMessage) -> Vec<(usize, SMessage)> {
        let mut msgs = Vec::new();
        match msg {
            CtlMessage::Extant(e) => {
                // TODO: Make this cleaner
                let mut ids = Vec::new();
                for r in e {
                    ids.push(r.id().to_owned());
                    self.subs.insert(r.id().to_owned(), HashSet::new());
                    let id = r.id().to_owned();
                    self.kinds[r.kind() as usize].insert(id.clone());
                    if let Some(tid) = r.torrent_id() {
                        if !self.torrent_idx.contains_key(tid) {
                            self.torrent_idx.insert(tid.to_owned(), HashSet::new());
                        }
                        self.torrent_idx.get_mut(tid).unwrap().insert(id.clone());
                    }
                    self.resources.insert(id, r);
                }
                // We have to make a new vec which points to the resource struct
                let mut rids = Vec::new();
                for id in ids {
                    rids.push(self.resources.get(&id).unwrap().id());
                }

                for (c, filters) in self.get_matching_filters(rids.into_iter()) {
                    for (serial, ids) in filters {
                        msgs.push((c, SMessage::ResourcesExtant { serial, ids }));
                    }
                }
            }
            CtlMessage::Update(updates) => {
                let mut clients = HashMap::new();
                for update in updates {
                    for c in self.subs.get(update.id()).unwrap().iter() {
                        if !clients.contains_key(c) {
                            clients.insert(*c, Vec::new());
                        }
                        clients.get_mut(c).unwrap().push(update.clone());
                    }
                    self.resources
                        .get_mut(update.id())
                        .expect("Bad resource updated by a CtlMessage")
                        .update(update);
                }
                for (c, resources) in clients {
                    msgs.push((c, SMessage::UpdateResources { resources }));
                }
            }
            CtlMessage::Removed(r) => {
                for (c, filters) in self.get_matching_filters(r.iter().map(|s| s.as_str())) {
                    for (serial, ids) in filters {
                        msgs.push((
                            c,
                            SMessage::ResourcesRemoved {
                                serial,
                                ids: ids.into_iter().map(|s| s.to_owned()).collect(),
                            },
                        ));
                    }
                }

                for id in r {
                    let r = self.resources.remove(&id).unwrap();
                    self.kinds[r.kind() as usize].remove(&id);
                    // If this resource is part of a torrent, remove from index,
                    // if we haven't removed the entire torrent already.
                    // Otherwise, attempt to remove the resource itself from the
                    // torrent index, since it's either a torrent or a server(ignored).
                    if let Some(tid) = r.torrent_id() {
                        self.torrent_idx.get_mut(tid).map(|s| s.remove(&id));
                    } else {
                        self.torrent_idx.remove(&id);
                    }
                }
            }
            CtlMessage::Shutdown => unreachable!(),
        }
        msgs
    }

    pub fn remove_client(&mut self, client: usize) {
        for (_, sub) in self.subs.iter_mut() {
            sub.remove(&client);
        }
        self.filter_subs.retain(|_, f| f.client != client);
    }

    /// Produces a map of the form Map<ClientId, Map<FilterSerial, Vec<ID>>>.
    fn get_matching_filters<'a, I: Iterator<Item = &'a str>>(
        &'a self,
        ids: I,
    ) -> HashMap<usize, HashMap<u64, Vec<&'a str>>> {
        let mut matched = HashMap::new();
        for id in ids {
            let res = self.resources.get(id).expect(
                "Bad resource requested from a CtlMessage",
            );
            for (s, f) in self.filter_subs.iter() {
                let c = f.client;
                if f.kind == res.kind() && f.matches(&res) {
                    if !matched.contains_key(&c) {
                        matched.insert(c, HashMap::new());
                    }
                    let filters = matched.get_mut(&c).unwrap();
                    if !filters.contains_key(s) {
                        filters.insert(*s, Vec::new());
                    }
                    filters.get_mut(s).unwrap().push(id);
                }
            }
        }
        matched
    }

    fn new_transfer(&mut self, client: usize, serial: u64, kind: TransferKind) -> SMessage {
        let expiration = Utc::now() + Duration::seconds(EXPIRATION_DUR);
        let tok = random_string(15);
        self.tokens.insert(
            tok.clone(),
            BearerToken {
                expiration,
                kind,
                serial,
                client,
            },
        );
        SMessage::TransferOffer {
            serial,
            expires: expiration,
            token: tok,
            // TODO: Get this
            size: 0,
        }
    }
}

impl Filter {
    pub fn matches(&self, r: &Resource) -> bool {
        if self.criteria.is_empty() {}
        self.criteria.iter().all(|c| r.matches(c))
    }
}
<commit_msg>Add indexing by kind and torrent id<commit_after>use std::collections::{HashMap, HashSet};

use chrono::{DateTime, Utc, Duration};

use super::proto::message::{CMessage, SMessage, Error};
use super::proto::criterion::{self, Criterion, ResourceKind, Filter as FTrait};
use super::proto::resource::{Resource, SResourceUpdate};
use super::{CtlMessage, Message};
use util::random_string;

pub struct Processor {
    subs: HashMap<String, HashSet<usize>>,
    filter_subs: HashMap<u64, Filter>,
    resources: HashMap<String, Resource>,
    // Index by resource kind
    kinds: Vec<HashSet<String>>,
    // Index by torrent ID
    torrent_idx: HashMap<String, HashSet<String>>,
    tokens: HashMap<String, BearerToken>,
}

struct Filter {
    kind: ResourceKind,
    criteria: Vec<Criterion>,
    client: usize,
}

struct BearerToken {
    expiration: DateTime<Utc>,
    client: usize,
    serial: u64,
    kind: TransferKind,
}

#[derive(Clone)]
pub enum TransferKind {
    UploadTorrent { size: u64, path: Option<String> },
    UploadFiles { size: u64, path: String },
    DownloadFile { path: String },
}

const EXPIRATION_DUR: i64 = 120;

impl Processor {
    pub fn new() -> Processor {
        Processor {
            subs: HashMap::new(),
            filter_subs: HashMap::new(),
            resources: HashMap::new(),
            tokens: HashMap::new(),
            torrent_idx: HashMap::new(),
            kinds: vec![HashSet::new(); 6],
        }
    }

    pub fn remove_expired_tokens(&mut self) {
        self.tokens.retain(|_, tok| tok.expiration > Utc::now())
    }

    pub fn get_transfer(&mut self, tok: String) -> Option<(usize, u64, TransferKind)> {
        let mut res = None;
        let rem = match self.tokens.get(&tok) {
            Some(bt) => {
                match &bt.kind {
                    s @ &TransferKind::UploadTorrent { .. } => {
                        res = Some((bt.client, bt.serial, s.clone()));
                        false
                    }
                    _ => true,
                }
            }
            None => {
                res = None;
                false
            }
        };
        if rem {
            let tok = self.tokens.remove(&tok).unwrap();
            res = Some((tok.client, tok.serial, tok.kind));
        }
        res
    }

    pub fn handle_client(
        &mut self,
        client: usize,
        msg: CMessage,
    ) -> (Vec<SMessage>, Option<Message>) {
        let mut resp = Vec::new();
        let mut rmsg = None;
        match msg {
            CMessage::GetResources { serial, ids } => {
                let mut resources = Vec::new();
                for id in ids {
                    if let Some(r) = self.resources.get(&id) {
                        resources.push(SResourceUpdate::Resource(r));
                    } else {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
                resp.push(SMessage::UpdateResources { resources });
            }
            CMessage::Subscribe { serial, ids } => {
                let mut resources = Vec::new();
                for id in ids {
                    if let Some(r) = self.resources.get(&id) {
                        resources.push(SResourceUpdate::Resource(r));
                        self.subs.get_mut(&id).map(|s| s.insert(client));
                    } else {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
                resp.push(SMessage::UpdateResources { resources });
            }
            CMessage::Unsubscribe { ids, .. } => {
                for id in ids {
                    self.subs.get_mut(&id).map(|s| s.remove(&client));
                }
            }
            CMessage::UpdateResource { serial, resource } => {
                match self.resources.get(&resource.id) {
                    Some(&Resource::Torrent(_)) => {
                        rmsg = Some(Message::UpdateTorrent(resource));
                    }
                    Some(&Resource::File(ref f)) => {
                        // TODO: Validate other fields(make sure they're not present)
                        if let Some(p) = resource.priority {
                            rmsg = Some(Message::UpdateFile {
                                id: resource.id,
                                torrent_id: f.torrent_id.to_owned(),
                                priority: p,
                            });
                        }
                    }
                    Some(&Resource::Server(_)) => {
                        rmsg = Some(Message::UpdateServer {
                            id: resource.id,
                            throttle_up: resource.throttle_up,
                            throttle_down: resource.throttle_down,
                        });
                    }
                    Some(_) => {
                        resp.push(SMessage::PermissionDenied(Error {
                            serial: Some(serial),
                            reason: format!("Only torrents, server, and files have mutable fields"),
                        }));
                    }
                    None => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", resource.id),
                        }));
                    }
                }
            }
            CMessage::RemoveResource { serial, id } => {
                match self.resources.get(&id) {
                    Some(&Resource::Torrent(_)) => {
                        rmsg = Some(Message::RemoveTorrent(id));
                    }
                    Some(&Resource::Tracker(ref t)) => {
                        rmsg = Some(Message::RemoveTracker {
                            id,
                            torrent_id: t.id.to_owned(),
                        });
                    }
                    Some(&Resource::Peer(ref p)) => {
                        rmsg = Some(Message::RemovePeer {
                            id,
                            torrent_id: p.id.to_owned(),
                        });
                    }
                    Some(_) => {
                        resp.push(SMessage::InvalidResource(Error {
                            serial: Some(serial),
                            reason: format!("Only torrents, trackers, and peers may be removed"),
                        }));
                    }
                    None => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown resource id {}", id),
                        }));
                    }
                }
            }
            CMessage::FilterSubscribe {
                serial,
                kind,
                criteria,
            } => {
                let f = Filter {
                    criteria,
                    kind,
                    client,
                };
                {
                    let mut valid = &self.kinds[kind as usize];
                    let crit_res = f.criteria.iter()
                        .find(|c| c.field == "torrent_id")
                        .and_then(|c| {
                            match &c.value {
                                &criterion::Value::S(ref s) => Some(s),
                                _ => None,
                            }
                        }).and_then(|id| self.torrent_idx.get(id));

                    let mut ids = Vec::new();
                    if let Some(t) = crit_res {
                        for id in valid.intersection(t) {
                            let r = self.resources.get(id).unwrap();
                            if f.matches(r) {
                                ids.push(r.id());
                            }
                        }
                    } else {
                        for id in valid.iter() {
                            let r = self.resources.get(id).unwrap();
                            if f.matches(r) {
                                ids.push(r.id());
                            }
                        }
                    }
                    resp.push(SMessage::ResourcesExtant { serial, ids });
                }
                self.filter_subs.insert(serial, f);
            }
            CMessage::FilterUnsubscribe { filter_serial, .. } => {
                self.filter_subs.remove(&filter_serial);
            }

            CMessage::UploadTorrent { serial, size, path } => {
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::UploadTorrent { size, path },
                ));
            }
            CMessage::UploadMagnet { serial, uri, path } => {}
            CMessage::UploadFiles { serial, size, path } => {
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::UploadFiles { size, path },
                ));
            }
            CMessage::DownloadFile { serial, id } => {
                let path = match self.resources.get(&id) {
                    Some(&Resource::File(ref f)) => f.path.clone(),
                    _ => {
                        resp.push(SMessage::UnknownResource(Error {
                            serial: Some(serial),
                            reason: format!("unknown file id {}", id),
                        }));
                        return (resp, rmsg);
                    }
                };
                resp.push(self.new_transfer(
                    client,
                    serial,
                    TransferKind::DownloadFile { path },
                ));
            }
        }
        (resp, rmsg)
    }

    pub fn handle_ctl(&mut self, msg: CtlMessage) -> Vec<(usize, SMessage)> {
        let mut msgs = Vec::new();
        match msg {
            CtlMessage::Extant(e) => {
                // TODO: Make this cleaner
                let mut ids = Vec::new();
                for r in e {
                    ids.push(r.id().to_owned());
                    self.subs.insert(r.id().to_owned(), HashSet::new());
                    let id = r.id().to_owned();
                    self.kinds[r.kind() as usize].insert(id.clone());
                    if let Some(tid) = r.torrent_id() {
                        if !self.torrent_idx.contains_key(tid) {
                            self.torrent_idx.insert(tid.to_owned(), HashSet::new());
                        }
                        self.torrent_idx.get_mut(tid).unwrap().insert(id.clone());
                    }
                    self.resources.insert(id, r);
                }
                // We have to make a new vec which points to the resource struct
                let mut rids = Vec::new();
                for id in ids {
                    rids.push(self.resources.get(&id).unwrap().id());
                }

                for (c, filters) in self.get_matching_filters(rids.into_iter()) {
                    for (serial, ids) in filters {
                        msgs.push((c, SMessage::ResourcesExtant { serial, ids }));
                    }
                }
            }
            CtlMessage::Update(updates) => {
                let mut clients = HashMap::new();
                for update in updates {
                    for c in self.subs.get(update.id()).unwrap().iter() {
                        if !clients.contains_key(c) {
                            clients.insert(*c, Vec::new());
                        }
                        clients.get_mut(c).unwrap().push(update.clone());
                    }
                    self.resources
                        .get_mut(update.id())
                        .expect("Bad resource updated by a CtlMessage")
                        .update(update);
                }
                for (c, resources) in clients {
                    msgs.push((c, SMessage::UpdateResources { resources }));
                }
            }
            CtlMessage::Removed(r) => {
                for (c, filters) in self.get_matching_filters(r.iter().map(|s| s.as_str())) {
                    for (serial, ids) in filters {
                        msgs.push((
                            c,
                            SMessage::ResourcesRemoved {
                                serial,
                                ids: ids.into_iter().map(|s| s.to_owned()).collect(),
                            },
                        ));
                    }
                }

                for id in r {
                    let r = self.resources.remove(&id).unwrap();
                    self.kinds[r.kind() as usize].remove(&id);
                    // If this resource is part of a torrent, remove from index,
                    // if we haven't removed the entire torrent already.
                    // Otherwise, attempt to remove the resource itself from the
                    // torrent index, since it's either a torrent or a server(ignored).
                    if let Some(tid) = r.torrent_id() {
                        self.torrent_idx.get_mut(tid).map(|s| s.remove(&id));
                    } else {
                        self.torrent_idx.remove(&id);
                    }
                }
            }
            CtlMessage::Shutdown => unreachable!(),
        }
        msgs
    }

    pub fn remove_client(&mut self, client: usize) {
        for (_, sub) in self.subs.iter_mut() {
            sub.remove(&client);
        }
        self.filter_subs.retain(|_, f| f.client != client);
    }

    /// Produces a map of the form Map<ClientId, Map<FilterSerial, Vec<ID>>>.
    fn get_matching_filters<'a, I: Iterator<Item = &'a str>>(
        &'a self,
        ids: I,
    ) -> HashMap<usize, HashMap<u64, Vec<&'a str>>> {
        let mut matched = HashMap::new();
        for id in ids {
            let res = self.resources.get(id).expect(
                "Bad resource requested from a CtlMessage",
            );
            for (s, f) in self.filter_subs.iter() {
                let c = f.client;
                if f.kind == res.kind() && f.matches(&res) {
                    if !matched.contains_key(&c) {
                        matched.insert(c, HashMap::new());
                    }
                    let filters = matched.get_mut(&c).unwrap();
                    if !filters.contains_key(s) {
                        filters.insert(*s, Vec::new());
                    }
                    filters.get_mut(s).unwrap().push(id);
                }
            }
        }
        matched
    }

    fn new_transfer(&mut self, client: usize, serial: u64, kind: TransferKind) -> SMessage {
        let expiration = Utc::now() + Duration::seconds(EXPIRATION_DUR);
        let tok = random_string(15);
        self.tokens.insert(
            tok.clone(),
            BearerToken {
                expiration,
                kind,
                serial,
                client,
            },
        );
        SMessage::TransferOffer {
            serial,
            expires: expiration,
            token: tok,
            // TODO: Get this
            size: 0,
        }
    }
}

impl Filter {
    pub fn matches(&self, r: &Resource) -> bool {
        if self.criteria.is_empty() {}
        self.criteria.iter().all(|c| r.matches(c))
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;

use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum UserFlag {
    /// User wants notification messages for reviews with approvals.
    NotifyReviewApprovals,
    /// User wants notification messages for review comments without approvals.
    NotifyReviewComments,
    /// User wants notification messages for reviews with inline comments.
    NotifyReviewInlineComments,
    /// User wants notification messages when added as reviewer to a change.
    NotifyReviewerAdded,
    /// User wants notification messages for review comments without approvals.
    NotifyReviewResponses,
    /// User wants notification messages for merged changes.
    NotifyChangeMerged,
    /// User wants notification messages for abandoned changes.
    NotifyChangeAbandoned,
}

impl Display for UserFlag {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        if let Ok(serde_json::Value::String(s)) = serde_json::to_value(self) {
            write!(f, "{}", s)
        } else {
            panic!("failed to encode flag")
        }
    }
}

impl FromStr for UserFlag {
    type Err = serde_json::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_slice(format!("\"{}\"", s).as_bytes())
    }
}

#[cfg(test)]
mod test_flag {
    use super::UserFlag;

    macro_rules! test_from_to_string {
        ($name:ident, $s:expr, $f:expr $( , )?) => {
            #[test]
            fn $name() {
                assert_eq!($s.parse::<UserFlag>().expect("parse failed"), $f);
                assert_eq!($f.to_string(), $s);
            }
        };
    }
    macro_rules! test_parse_fail {
        ($name:ident, $s:expr) => {
            #[test]
            fn $name() {
                $s.parse::<UserFlag>().expect_err("did not fail");
            }
        };
    }

    test_from_to_string!(
        notify_review_approvals,
        "notify_review_approvals",
        UserFlag::NotifyReviewApprovals,
    );
    test_from_to_string!(
        notify_review_comments,
        "notify_review_comments",
        UserFlag::NotifyReviewComments,
    );
    test_from_to_string!(
        notify_review_inline_comments,
        "notify_review_inline_comments",
        UserFlag::NotifyReviewInlineComments,
    );
    test_from_to_string!(
        notify_reviewer_added,
        "notify_reviewer_added",
        UserFlag::NotifyReviewerAdded,
    );

    test_parse_fail!(unknown_flag, "unknown_flag");
    test_parse_fail!(integer, "123");
    test_parse_fail!(quotation_mark, "\"");
}

/// Default flags for users that haven't enabled or disabled anything specific.
const DEFAULT_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewInlineComments,
    UserFlag::NotifyReviewerAdded,
];

/// All flags that deal with review comments.
pub const REVIEW_COMMENT_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewComments,
    UserFlag::NotifyReviewInlineComments,
];

/// All flags that deal with notifications.
pub const NOTIFICATION_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewComments,
    UserFlag::NotifyReviewInlineComments,
    UserFlag::NotifyReviewerAdded,
    UserFlag::NotifyReviewResponses,
    UserFlag::NotifyChangeMerged,
    UserFlag::NotifyChangeAbandoned,
];

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(super) enum UserFlags {
    Default,
    // Note: this could be optimized into bitflags to make it faster and avoid
    // allocation.
    Custom(HashSet<UserFlag>),
}

impl Default for UserFlags {
    fn default() -> Self {
        UserFlags::Default
    }
}

impl UserFlags {
    pub fn contains(&self, flag: UserFlag) -> bool {
        match self {
            UserFlags::Default => DEFAULT_FLAGS
                .iter()
                .any(|default_flag| default_flag == &flag),
            UserFlags::Custom(set) => set.contains(&flag),
        }
    }

    pub fn is_default(&self) -> bool {
        if let UserFlags::Default = self {
            true
        } else {
            false
        }
    }

    pub fn reset(&mut self) {
        std::mem::replace(self, UserFlags::Default);
    }

    pub fn set(&mut self, flag: UserFlag, value: bool) {
        let set_flag = |set: &mut HashSet<UserFlag>| {
            if value {
                set.insert(flag);
            } else {
                set.remove(&flag);
            }
        };

        match self {
            UserFlags::Default => {
                let mut set = DEFAULT_FLAGS.iter().cloned().collect();
                set_flag(&mut set);
                std::mem::replace(self, UserFlags::Custom(set));
            }
            UserFlags::Custom(ref mut set) => set_flag(set),
        }
    }
}
<commit_msg>change user flags serialization to untagged<commit_after>use std::collections::HashSet;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;

use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum UserFlag {
    /// User wants notification messages for reviews with approvals.
    NotifyReviewApprovals,
    /// User wants notification messages for review comments without approvals.
    NotifyReviewComments,
    /// User wants notification messages for reviews with inline comments.
    NotifyReviewInlineComments,
    /// User wants notification messages when added as reviewer to a change.
    NotifyReviewerAdded,
    /// User wants notification messages for review comments without approvals.
    NotifyReviewResponses,
    /// User wants notification messages for merged changes.
    NotifyChangeMerged,
    /// User wants notification messages for abandoned changes.
    NotifyChangeAbandoned,
}

impl Display for UserFlag {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        if let Ok(serde_json::Value::String(s)) = serde_json::to_value(self) {
            write!(f, "{}", s)
        } else {
            panic!("failed to encode flag")
        }
    }
}

impl FromStr for UserFlag {
    type Err = serde_json::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_slice(format!("\"{}\"", s).as_bytes())
    }
}

#[cfg(test)]
mod test_flag {
    use super::UserFlag;

    macro_rules! test_from_to_string {
        ($name:ident, $s:expr, $f:expr $( , )?) => {
            #[test]
            fn $name() {
                assert_eq!($s.parse::<UserFlag>().expect("parse failed"), $f);
                assert_eq!($f.to_string(), $s);
            }
        };
    }
    macro_rules! test_parse_fail {
        ($name:ident, $s:expr) => {
            #[test]
            fn $name() {
                $s.parse::<UserFlag>().expect_err("did not fail");
            }
        };
    }

    test_from_to_string!(
        notify_review_approvals,
        "notify_review_approvals",
        UserFlag::NotifyReviewApprovals,
    );
    test_from_to_string!(
        notify_review_comments,
        "notify_review_comments",
        UserFlag::NotifyReviewComments,
    );
    test_from_to_string!(
        notify_review_inline_comments,
        "notify_review_inline_comments",
        UserFlag::NotifyReviewInlineComments,
    );
    test_from_to_string!(
        notify_reviewer_added,
        "notify_reviewer_added",
        UserFlag::NotifyReviewerAdded,
    );

    test_parse_fail!(unknown_flag, "unknown_flag");
    test_parse_fail!(integer, "123");
    test_parse_fail!(quotation_mark, "\"");
}

/// Default flags for users that haven't enabled or disabled anything specific.
const DEFAULT_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewInlineComments,
    UserFlag::NotifyReviewerAdded,
];

/// All flags that deal with review comments.
pub const REVIEW_COMMENT_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewComments,
    UserFlag::NotifyReviewInlineComments,
];

/// All flags that deal with notifications.
pub const NOTIFICATION_FLAGS: &[UserFlag] = &[
    UserFlag::NotifyReviewApprovals,
    UserFlag::NotifyReviewComments,
    UserFlag::NotifyReviewInlineComments,
    UserFlag::NotifyReviewerAdded,
    UserFlag::NotifyReviewResponses,
    UserFlag::NotifyChangeMerged,
    UserFlag::NotifyChangeAbandoned,
];

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub(super) enum UserFlags {
    Default,
    // Note: this could be optimized into bitflags to make it faster and avoid
    // allocation.
    Custom(HashSet<UserFlag>),
}

impl Default for UserFlags {
    fn default() -> Self {
        UserFlags::Default
    }
}

impl UserFlags {
    pub fn contains(&self, flag: UserFlag) -> bool {
        match self {
            UserFlags::Default => DEFAULT_FLAGS
                .iter()
                .any(|default_flag| default_flag == &flag),
            UserFlags::Custom(set) => set.contains(&flag),
        }
    }

    pub fn is_default(&self) -> bool {
        if let UserFlags::Default = self {
            true
        } else {
            false
        }
    }

    pub fn reset(&mut self) {
        std::mem::replace(self, UserFlags::Default);
    }

    pub fn set(&mut self, flag: UserFlag, value: bool) {
        let set_flag = |set: &mut HashSet<UserFlag>| {
            if value {
                set.insert(flag);
            } else {
                set.remove(&flag);
            }
        };

        match self {
            UserFlags::Default => {
                let mut set = DEFAULT_FLAGS.iter().cloned().collect();
                set_flag(&mut set);
                std::mem::replace(self, UserFlags::Custom(set));
            }
            UserFlags::Custom(ref mut set) => set_flag(set),
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2013-2014 Jeffery Olson
//
// Licensed under the 3-Clause BSD License, see LICENSE.txt
// at the top-level of this repository.
// This file may not be copied, modified, or distributed
// except according to those terms.
#![crate_id="http://olsonjeffery.github.io#schemers:0.0.1"]
#![desc = "Simple Scheme Interpreter"]
#![license = "3-Clause BSD"]

extern crate collections = "collections";

use std::from_str::FromStr;
use collections::hashmap::HashMap;

// this is starting out as a straight port of Peter Norvig's
// lis.py (the first iteration) to Rust

#[cfg(not(test))]
fn main() {
    use std::os::args;
    let args: Vec<~str> = args().move_iter().skip(1).collect();
    if args.len() == 0 {
        fail!("no program provided");
    }
    let program: ~str = args.move_iter()
        .fold(~"", |memo, arg| memo + arg + " ").trim().to_owned();
    let parsed_items = parse_str(program);
    println!("{}", parsed_items.print());
}

pub struct Env {
    entries: HashMap<~str, Expr>,
    outer: Option<~Env>
}

#[deriving(Eq, Show, Clone)]
pub enum Expr {
    Atom(AtomVal),
    List(Vec<~Expr>)
}

#[deriving(Eq, Show, Clone)]
pub enum AtomVal {
    Symbol(~str),
    Integer(i64),
    Float(f64)
}

fn parse_str(input: ~str) -> Expr {
    parse(&mut tokenize(input))
}

fn pad_input(input: ~str) -> ~str {
    input.replace("(", " ( ").replace(")", " ) ")
}

fn tokenize(input: ~str) -> Vec<~str> {
    pad_input(input).split_str(" ")
        .filter(|i| *i != "").map(|i| i.to_owned()).collect()
}

fn parse(tokens: &mut Vec<~str>) -> Expr {
    let current_token =
        tokens.shift().expect("calling parse() w/ empty token list; shouldn't happen");
    match current_token {
        ref x if *x == ~"(" => {
            let mut list = Vec::new();
            while *tokens.get(0) != ~")" {
                list.push(box parse(tokens));
            }
            tokens.shift();
            List(list)
        },
        ref x if *x == ~")" => fail!("hit ) token; shouldn't happen"),
        x => Expr::new_atom(x)
    }
}

impl Env {
    pub fn new(params: Option<Vec<~str>>, args: Option<Vec<Expr>>, outer: Option<~Env>) -> Env {
        let mut entries = HashMap::new();
        if params.is_some() && args.is_some() {
            let params = params.expect("params should be a value");
            let args = args.expect("args should be a value");
            if params.len() == args.len() {
                for ctr in range(0,params.len()) {
                    entries.insert(params.get(ctr).to_owned(),
                                   args.get(ctr).clone());
                }
            } else {
                fail!("params and args length doesn't match")
            }
        }
        else if params.is_none() && args.is_none() {
            // do nothing!
        } else {
            fail!("cannot have params & args unset")
        }
        Env { entries: entries, outer: outer }
    }
}

impl Expr {
    pub fn new_atom(input: ~str) -> Expr {
        let first_char = input.char_at(0);
        match first_char {
            '0' | '1' | '2' | '3' | '4' | '5' |
            '6' | '7' | '8' | '9' => {
                match input.contains(".") {
                    true => {
                        match FromStr::from_str(input) {
                            Some(val) => Atom(Float(val)),
                            None => fail!("Cannot parse number-like input of: {}", input)
                        }
                    },
                    false => {
                        match FromStr::from_str(input) {
                            Some(val) => Atom(Integer(val)),
                            None => fail!("Cannot parse number-like input of: {}", input)
                        }
                    }
                }
            },
            _ => Atom(Symbol(input))
        }
    }

    pub fn is_atom(&self) -> bool {
        match *self {
            Atom(_) => true,
            _ => false
        }
    }

    pub fn print(&self) -> ~str {
        match self {
            &List(ref items) => {
                let out = items.iter().map(|i| i.print())
                    .fold(~"(", |m, v| m + v + " ");
                out.trim() + ")"
            },
            &Atom(ref v) => v.print()
        }
    }
    pub fn un_cons(self) -> (Expr, Option<Expr>) {
        match self {
            Atom(v) => (Atom(v), None),
            List(mut items) => {
                if items.len() == 0 {
                    fail!("cannot build car/cdr of empty list");
                }
                let car = items.shift()
                    .expect("at least one item in the least; shouldn't happen");
                (*car, Some(List(items)))
            }
        }
    }
}

impl AtomVal {
    pub fn print(&self) -> ~str {
        match self {
            &Symbol(ref v) => v.to_owned(),
            &Integer(ref v) => v.to_str(),
            &Float(ref v) => v.to_str()
        }
    }
}

#[cfg(test)]
mod parser_test {
    use super::{pad_input, tokenize, parse, Atom, List, Symbol, Integer, Float};

    #[test]
    fn pad_input_should_insert_spaces_before_and_after_parens() {
        assert_eq!(pad_input(~"(x 1 2 3)"), ~" ( x 1 2 3 ) ");
    }

    #[test]
    fn tokenize_string_into_tokens() {
        let tokens = tokenize(~"(foo 12 3)");
        assert_eq!(tokens.len(), 5);
        assert_eq!(tokens.get(0).to_owned(), ~"(");
        assert_eq!(tokens.get(1).to_owned(), ~"foo");
        assert_eq!(tokens.get(2).to_owned(), ~"12");
        assert_eq!(tokens.get(3).to_owned(), ~"3");
        assert_eq!(tokens.get(4).to_owned(), ~")");
    }

    #[test]
    fn should_parse_tokens_consisting_of_a_str_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"bar");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Symbol(val)) => {
                assert_eq!(val, ~"bar")
            },
            _ => assert!(false)
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_an_int_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"42");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Integer(val)) => {
                assert_eq!(val, 42)
            },
            _ => assert!(false)
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_a_float_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"1.1");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Float(val)) => {
                assert_eq!(val, 1.1)
            },
            _ => assert!(false)
        }
    }

    #[test]
    fn should_parse_tokens_consisting_of_a_list_of_atoms_and_parse_it() {
        let tokens = &mut tokenize(~"(bar 12 45)");
        let parsed_item = parse(tokens);
        assert_eq!(parsed_item.is_atom(), false);
        match parsed_item {
            List(items) => {
                let items_len = items.len();
                assert!(3 == items_len);
                assert_eq!(items.get(0).is_atom(), true);
                assert_eq!(*items.get(0), ~Atom(Symbol(~"bar")));
                assert_eq!(items.get(1).is_atom(), true);
                assert_eq!(*items.get(1), ~Atom(Integer(12)));
                assert_eq!(items.get(2).is_atom(), true);
                assert_eq!(*items.get(2), ~Atom(Integer(45)));
            },
            _ => fail!("got back an atom, it seems")
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_a_list_of_atoms_and_a_nested_list() {
        let tokens = &mut tokenize(~"((2 3) bar 12 (hee (hah)))");
        let parsed_item = parse(tokens);
        assert_eq!(parsed_item.is_atom(), false);
        match parsed_item {
            List(items) => {
                let items_len = items.len();
                assert!(4 == items_len);
                match *items.get(0) {
                    ~List(ref items) => {
                        assert_eq!(items.len(), 2);
                        assert_eq!(*items.get(0), ~Atom(Integer(2)));
                        assert_eq!(*items.get(1), ~Atom(Integer(3)));
                    },
                    _ => fail!("shoulda got a list")
                }
                assert_eq!(*items.get(1), ~Atom(Symbol(~"bar")));
                assert_eq!(*items.get(2), ~Atom(Integer(12)));
                match *items.get(3) {
                    ~List(ref items) => {
                        assert_eq!(items.len(), 2);
                        assert_eq!(*items.get(0), ~Atom(Symbol(~"hee")));
                        match *items.get(1) {
                            ~List(ref items) => {
                                assert_eq!(items.len(), 1);
                                assert_eq!(*items.get(0), ~Atom(Symbol(~"hah")));
                            },
                            _ => fail!("shoulda got a list")
                        }
                    },
                    _ => fail!("shoulda got a list")
                }
            },
            _ => fail!("got back an atom, it seems")
        }
    }
    #[test]
    fn parsed_tokens_of_a_list_of_atoms_and_a_nested_list_should_to_str_correctly() {
        let input = ~"((2 3) bar 12 (hee (hah)))";
        let tokens = &mut tokenize(input.to_owned());
        let parsed_item = parse(tokens);
        let output = parsed_item.print();
        assert_eq!(input, output);
    }
}

#[cfg(test)]
mod eval_test {
    mod un_cons {
        use super::super::{parse_str, Atom, List, Symbol};
        #[test]
        fn an_atom_expr_returns_the_atom_in_the_car_with_none_in_the_cdr() {
            let expr = parse_str(~"x");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"x")));
            assert_eq!(cdr, None);
        }

        #[test]
        fn a_list_with_one_atom_element_returns_an_atom_car_and_an_empty_list_in_the_cdr() {
            let expr = parse_str(~"(x)");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"x")));
            match cdr.expect("should be a value here") {
                List(items) => assert_eq!(items.len(), 0),
                _ => fail!("expected a list")
            }
        }

        #[test]
        fn a_list_with_multiple_elements_returns_the_first_car_and_the_rust_in_the_cdr() {
            let expr = parse_str(~"(y 2 3)");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"y")));
            match cdr.expect("should be a value here") {
                List(items) => {
                    assert_eq!(items.len(), 2)
                },
                _ => fail!("expected a list")
            }
        }

        #[test]
        #[should_fail]
        fn calling_extract_on_an_empty_list_should_fail() {
            let expr = parse_str(~"()");
            expr.un_cons();
        }
    }

    mod env_tests {
        use super::super::{Env};

        #[test]
        fn a_new_env_with_no_parent_params_or_vars_should_be_set_up_empty() {
            let _env = Env::new(None, None, None);
        }
    }
}
<commit_msg>test+impl for eval'ing a variable in the Env<commit_after>// Copyright 2013-2014 Jeffery Olson
//
// Licensed under the 3-Clause BSD License, see LICENSE.txt
// at the top-level of this repository.
// This file may not be copied, modified, or distributed
// except according to those terms.
#![crate_id="http://olsonjeffery.github.io#schemers:0.0.1"]
#![desc = "Simple Scheme Interpreter"]
#![license = "3-Clause BSD"]

extern crate collections = "collections";

use std::from_str::FromStr;
use collections::hashmap::HashMap;

// this is starting out as a straight port of Peter Norvig's
// lis.py (the first iteration) to Rust

#[cfg(not(test))]
fn main() {
    use std::os::args;
    let args: Vec<~str> = args().move_iter().skip(1).collect();
    if args.len() == 0 {
        fail!("no program provided");
    }
    let program: ~str = args.move_iter()
        .fold(~"", |memo, arg| memo + arg + " ").trim().to_owned();
    let parsed_items = parse_str(program);
    println!("{}", parsed_items.print());
}

pub struct Env {
    entries: HashMap<~str, Expr>,
    outer: Option<~Env>
}

#[deriving(Eq, Show, Clone)]
pub enum Expr {
    Atom(AtomVal),
    List(Vec<~Expr>)
}

#[deriving(Eq, Show, Clone)]
pub enum AtomVal {
    Symbol(~str),
    Integer(i64),
    Float(f64)
}

fn eval(expr: Expr, env: Env) -> (Expr, Env) {
    match expr {
        Atom(Symbol(ref var_name)) => (env.find(var_name), env),
        _ => fail!("un-implemented case")
    }
}

fn parse_str(input: ~str) -> Expr {
    parse(&mut tokenize(input))
}

fn pad_input(input: ~str) -> ~str {
    input.replace("(", " ( ").replace(")", " ) ")
}

fn tokenize(input: ~str) -> Vec<~str> {
    pad_input(input).split_str(" ")
        .filter(|i| *i != "").map(|i| i.to_owned()).collect()
}

fn parse(tokens: &mut Vec<~str>) -> Expr {
    let current_token =
        tokens.shift().expect("calling parse() w/ empty token list; shouldn't happen");
    match current_token {
        ref x if *x == ~"(" => {
            let mut list = Vec::new();
            while *tokens.get(0) != ~")" {
                list.push(box parse(tokens));
            }
            tokens.shift();
            List(list)
        },
        ref x if *x == ~")" => fail!("hit ) token; shouldn't happen"),
        x => Expr::new_atom(x)
    }
}

impl Env {
    pub fn new(params: Option<Vec<~str>>, args: Option<Vec<Expr>>, outer: Option<~Env>) -> Env {
        let mut entries = HashMap::new();
        if params.is_some() && args.is_some() {
            let params = params.expect("params should be a value");
            let args = args.expect("args should be a value");
            if params.len() == args.len() {
                for ctr in range(0,params.len()) {
                    entries.insert(params.get(ctr).to_owned(),
                                   args.get(ctr).clone());
                }
            } else {
                fail!("params and args length doesn't match")
            }
        }
        else if params.is_none() && args.is_none() {
            // do nothing!
        } else {
            fail!("cannot have params & args unset")
        }
        Env { entries: entries, outer: outer }
    }

    pub fn find(&self, symbol: &~str) -> Expr {
        match self.entries.find(symbol) {
            Some(expr) => expr.clone(),
            None => {
                match &self.outer {
                    &Some(ref outer_env) => outer_env.find(symbol),
                    &None => fail!("No variable named {} defined in the environment."
                                  , *symbol)
                }
            }
        }
    }
}

impl Expr {
    pub fn new_atom(input: ~str) -> Expr {
        let first_char = input.char_at(0);
        match first_char {
            '0' | '1' | '2' | '3' | '4' | '5' |
            '6' | '7' | '8' | '9' => {
                match input.contains(".") {
                    true => {
                        match FromStr::from_str(input) {
                            Some(val) => Atom(Float(val)),
                            None => fail!("Cannot parse number-like input of: {}", input)
                        }
                    },
                    false => {
                        match FromStr::from_str(input) {
                            Some(val) => Atom(Integer(val)),
                            None => fail!("Cannot parse number-like input of: {}", input)
                        }
                    }
                }
            },
            _ => Atom(Symbol(input))
        }
    }

    pub fn is_atom(&self) -> bool {
        match *self {
            Atom(_) => true,
            _ => false
        }
    }

    pub fn print(&self) -> ~str {
        match self {
            &List(ref items) => {
                let out = items.iter().map(|i| i.print())
                    .fold(~"(", |m, v| m + v + " ");
                out.trim() + ")"
            },
            &Atom(ref v) => v.print()
        }
    }
    pub fn un_cons(self) -> (Expr, Option<Expr>) {
        match self {
            Atom(v) => (Atom(v), None),
            List(mut items) => {
                if items.len() == 0 {
                    fail!("cannot build car/cdr of empty list");
                }
                let car = items.shift()
                    .expect("at least one item in the least; shouldn't happen");
                (*car, Some(List(items)))
            }
        }
    }
}

impl AtomVal {
    pub fn print(&self) -> ~str {
        match self {
            &Symbol(ref v) => v.to_owned(),
            &Integer(ref v) => v.to_str(),
            &Float(ref v) => v.to_str()
        }
    }
}

#[cfg(test)]
mod parser_test {
    use super::{pad_input, tokenize, parse, Atom, List, Symbol, Integer, Float};

    #[test]
    fn pad_input_should_insert_spaces_before_and_after_parens() {
        assert_eq!(pad_input(~"(x 1 2 3)"), ~" ( x 1 2 3 ) ");
    }

    #[test]
    fn tokenize_string_into_tokens() {
        let tokens = tokenize(~"(foo 12 3)");
        assert_eq!(tokens.len(), 5);
        assert_eq!(tokens.get(0).to_owned(), ~"(");
        assert_eq!(tokens.get(1).to_owned(), ~"foo");
        assert_eq!(tokens.get(2).to_owned(), ~"12");
        assert_eq!(tokens.get(3).to_owned(), ~"3");
        assert_eq!(tokens.get(4).to_owned(), ~")");
    }

    #[test]
    fn should_parse_tokens_consisting_of_a_str_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"bar");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Symbol(val)) => {
                assert_eq!(val, ~"bar")
            },
            _ => assert!(false)
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_an_int_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"42");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Integer(val)) => {
                assert_eq!(val, 42)
            },
            _ => assert!(false)
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_a_float_atom_and_convert_it_to_a_parse_item() {
        let tokens = &mut tokenize(~"1.1");
        let parsed_item = parse(tokens);
        println!("{:?}", parsed_item);
        match parsed_item {
            Atom(Float(val)) => {
                assert_eq!(val, 1.1)
            },
            _ => assert!(false)
        }
    }

    #[test]
    fn should_parse_tokens_consisting_of_a_list_of_atoms_and_parse_it() {
        let tokens = &mut tokenize(~"(bar 12 45)");
        let parsed_item = parse(tokens);
        assert_eq!(parsed_item.is_atom(), false);
        match parsed_item {
            List(items) => {
                let items_len = items.len();
                assert!(3 == items_len);
                assert_eq!(items.get(0).is_atom(), true);
                assert_eq!(*items.get(0), ~Atom(Symbol(~"bar")));
                assert_eq!(items.get(1).is_atom(), true);
                assert_eq!(*items.get(1), ~Atom(Integer(12)));
                assert_eq!(items.get(2).is_atom(), true);
                assert_eq!(*items.get(2), ~Atom(Integer(45)));
            },
            _ => fail!("got back an atom, it seems")
        }
    }
    #[test]
    fn should_parse_tokens_consisting_of_a_list_of_atoms_and_a_nested_list() {
        let tokens = &mut tokenize(~"((2 3) bar 12 (hee (hah)))");
        let parsed_item = parse(tokens);
        assert_eq!(parsed_item.is_atom(), false);
        match parsed_item {
            List(items) => {
                let items_len = items.len();
                assert!(4 == items_len);
                match *items.get(0) {
                    ~List(ref items) => {
                        assert_eq!(items.len(), 2);
                        assert_eq!(*items.get(0), ~Atom(Integer(2)));
                        assert_eq!(*items.get(1), ~Atom(Integer(3)));
                    },
                    _ => fail!("shoulda got a list")
                }
                assert_eq!(*items.get(1), ~Atom(Symbol(~"bar")));
                assert_eq!(*items.get(2), ~Atom(Integer(12)));
                match *items.get(3) {
                    ~List(ref items) => {
                        assert_eq!(items.len(), 2);
                        assert_eq!(*items.get(0), ~Atom(Symbol(~"hee")));
                        match *items.get(1) {
                            ~List(ref items) => {
                                assert_eq!(items.len(), 1);
                                assert_eq!(*items.get(0), ~Atom(Symbol(~"hah")));
                            },
                            _ => fail!("shoulda got a list")
                        }
                    },
                    _ => fail!("shoulda got a list")
                }
            },
            _ => fail!("got back an atom, it seems")
        }
    }
    #[test]
    fn parsed_tokens_of_a_list_of_atoms_and_a_nested_list_should_to_str_correctly() {
        let input = ~"((2 3) bar 12 (hee (hah)))";
        let tokens = &mut tokenize(input.to_owned());
        let parsed_item = parse(tokens);
        let output = parsed_item.print();
        assert_eq!(input, output);
    }
}

#[cfg(test)]
mod eval_test {
    mod un_cons {
        use super::super::{parse_str, Atom, List, Symbol, Integer, Env, eval};
        #[test]
        fn an_atom_expr_returns_the_atom_in_the_car_with_none_in_the_cdr() {
            let expr = parse_str(~"x");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"x")));
            assert_eq!(cdr, None);
        }

        #[test]
        fn a_list_with_one_atom_element_returns_an_atom_car_and_an_empty_list_in_the_cdr() {
            let expr = parse_str(~"(x)");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"x")));
            match cdr.expect("should be a value here") {
                List(items) => assert_eq!(items.len(), 0),
                _ => fail!("expected a list")
            }
        }

        #[test]
        fn a_list_with_multiple_elements_returns_the_first_car_and_the_rust_in_the_cdr() {
            let expr = parse_str(~"(y 2 3)");
            let (car, cdr) = expr.un_cons();
            assert_eq!(car, Atom(Symbol(~"y")));
            match cdr.expect("should be a value here") {
                List(items) => {
                    assert_eq!(items.len(), 2)
                },
                _ => fail!("expected a list")
            }
        }

        #[test]
        #[should_fail]
        fn calling_extract_on_an_empty_list_should_fail() {
            let expr = parse_str(~"()");
            expr.un_cons();
        }

        #[test]
        fn given_a_symbol_in_the_env_then_calling_eval_should_return_it() {
            let env = Env::new(
                Some(vec!(~"x")),
                Some(vec!(Atom(Integer(42)))),
                None);
            let in_expr = parse_str(~"x");
            let (out_expr, _) = eval(in_expr, env);
            assert_eq!(out_expr, Atom(Integer(42)));
        }

        #[test]
        #[should_fail]
        fn given_a_symbol_NOT_in_the_env_then_calling_eval_should_fail() {
            let env = Env::new(None, None, None);
            let in_expr = parse_str(~"x");
            eval(in_expr, env);
        }
    }

    mod env_tests {
        use super::super::{Env};

        #[test]
        fn a_new_env_with_no_parent_params_or_vars_should_be_set_up_empty() {
            let _env = Env::new(None, None, None);
        }
    }
}
<|endoftext|>


<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.
// spell-checker:ignore itotal iused iavail ipcent pcent squashfs
use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE};
use clap::ArgMatches;

/// The columns in the output table produced by `df`.
///
/// The [`Row`] struct has a field corresponding to each of the
/// variants of this enumeration.
///
/// [`Row`]: crate::table::Row
#[derive(PartialEq, Copy, Clone)]
pub(crate) enum Column {
    /// The source of the mount point, usually a device.
    Source,

    /// Total number of blocks.
    Size,

    /// Number of used blocks.
    Used,

    /// Number of available blocks.
    Avail,

    /// Percentage of blocks used out of total number of blocks.
    Pcent,

    /// The mount point.
    Target,

    /// Total number of inodes.
    Itotal,

    /// Number of used inodes.
    Iused,

    /// Number of available inodes.
    Iavail,

    /// Percentage of inodes used out of total number of inodes.
    Ipcent,

    /// The filename given as a command-line argument.
    File,

    /// The filesystem type, like "ext4" or "squashfs".
    Fstype,

    /// Percentage of bytes available to non-privileged processes.
    #[cfg(target_os = "macos")]
    Capacity,
}

/// An error while defining which columns to display in the output table.
#[derive(Debug)]
pub(crate) enum ColumnError {
    /// If a column appears more than once in the `--output` argument.
    MultipleColumns(String),
}

impl Column {
    /// Convert from command-line arguments to sequence of columns.
    ///
    /// The set of columns that will appear in the output table can be
    /// specified by command-line arguments. This function converts
    /// those arguments to a [`Vec`] of [`Column`] variants.
    ///
    /// # Errors
    ///
    /// This function returns an error if a column is specified more
    /// than once in the command-line argument.
    pub(crate) fn from_matches(matches: &ArgMatches) -> Result<Vec<Self>, ColumnError> {
        match (
            matches.is_present(OPT_PRINT_TYPE),
            matches.is_present(OPT_INODES),
            matches.occurrences_of(OPT_OUTPUT) > 0,
        ) {
            (false, false, false) => Ok(vec![
                Self::Source,
                Self::Size,
                Self::Used,
                Self::Avail,
                #[cfg(target_os = "macos")]
                Self::Capacity,
                Self::Pcent,
                Self::Target,
            ]),
            (false, false, true) => {
                // Unwrapping should not panic because in this arm of
                // the `match` statement, we know that `OPT_OUTPUT`
                // is non-empty.
                let names = matches.values_of(OPT_OUTPUT).unwrap();
                let mut seen: Vec<&str> = vec![];
                let mut columns = vec![];
                for name in names {
                    if seen.contains(&name) {
                        return Err(ColumnError::MultipleColumns(name.to_string()));
                    }
                    seen.push(name);
                    // Unwrapping here should not panic because the
                    // command-line argument parsing library should be
                    // responsible for ensuring each comma-separated
                    // string is a valid column label.
                    let column = Self::parse(name).unwrap();
                    columns.push(column);
                }
                Ok(columns)
            }
            (false, true, false) => Ok(vec![
                Self::Source,
                Self::Itotal,
                Self::Iused,
                Self::Iavail,
                Self::Ipcent,
                Self::Target,
            ]),
            (true, false, false) => Ok(vec![
                Self::Source,
                Self::Fstype,
                Self::Size,
                Self::Used,
                Self::Avail,
                #[cfg(target_os = "macos")]
                Self::Capacity,
                Self::Pcent,
                Self::Target,
            ]),
            (true, true, false) => Ok(vec![
                Self::Source,
                Self::Fstype,
                Self::Itotal,
                Self::Iused,
                Self::Iavail,
                Self::Ipcent,
                Self::Target,
            ]),
            // The command-line arguments -T and -i are each mutually
            // exclusive with --output, so the command-line argument
            // parser should reject those combinations before we get
            // to this point in the code.
            _ => unreachable!(),
        }
    }

    /// Convert a column name to the corresponding enumeration variant.
    ///
    /// There are twelve valid column names, one for each variant:
    ///
    /// - "source"
    /// - "fstype"
    /// - "itotal"
    /// - "iused"
    /// - "iavail"
    /// - "ipcent"
    /// - "size"
    /// - "used"
    /// - "avail"
    /// - "pcent"
    /// - "file"
    /// - "target"
    ///
    /// # Errors
    ///
    /// If the string `s` is not one of the valid column names.
    fn parse(s: &str) -> Result<Self, ()> {
        match s {
            "source" => Ok(Self::Source),
            "fstype" => Ok(Self::Fstype),
            "itotal" => Ok(Self::Itotal),
            "iused" => Ok(Self::Iused),
            "iavail" => Ok(Self::Iavail),
            "ipcent" => Ok(Self::Ipcent),
            "size" => Ok(Self::Size),
            "used" => Ok(Self::Used),
            "avail" => Ok(Self::Avail),
            "pcent" => Ok(Self::Pcent),
            "file" => Ok(Self::File),
            "target" => Ok(Self::Target),
            _ => Err(()),
        }
    }

    /// Return the alignment of the specified column.
    pub(crate) fn alignment(column: &Self) -> Alignment {
        match column {
            Self::Source | Self::Target | Self::File | Self::Fstype => Alignment::Left,
            _ => Alignment::Right,
        }
    }

    /// Return the minimum width of the specified column.
    pub(crate) fn min_width(column: &Self) -> usize {
        match column {
            // 14 = length of "Filesystem" plus 4 spaces
            Self::Source => 14,
            // the shortest headers have a length of 4 chars so we use that as the minimum width
            _ => 4,
        }
    }
}

/// A column's alignment.
///
/// We define our own `Alignment` enum instead of using `std::fmt::Alignment` because df doesn't
/// have centered columns and hence a `Center` variant is not needed.
pub(crate) enum Alignment {
    Left,
    Right,
}
<commit_msg>df: set min width of "Used" column to 5<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.
// spell-checker:ignore itotal iused iavail ipcent pcent squashfs
use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE};
use clap::ArgMatches;

/// The columns in the output table produced by `df`.
///
/// The [`Row`] struct has a field corresponding to each of the
/// variants of this enumeration.
///
/// [`Row`]: crate::table::Row
#[derive(PartialEq, Copy, Clone)]
pub(crate) enum Column {
    /// The source of the mount point, usually a device.
    Source,

    /// Total number of blocks.
    Size,

    /// Number of used blocks.
    Used,

    /// Number of available blocks.
    Avail,

    /// Percentage of blocks used out of total number of blocks.
    Pcent,

    /// The mount point.
    Target,

    /// Total number of inodes.
    Itotal,

    /// Number of used inodes.
    Iused,

    /// Number of available inodes.
    Iavail,

    /// Percentage of inodes used out of total number of inodes.
    Ipcent,

    /// The filename given as a command-line argument.
    File,

    /// The filesystem type, like "ext4" or "squashfs".
    Fstype,

    /// Percentage of bytes available to non-privileged processes.
    #[cfg(target_os = "macos")]
    Capacity,
}

/// An error while defining which columns to display in the output table.
#[derive(Debug)]
pub(crate) enum ColumnError {
    /// If a column appears more than once in the `--output` argument.
    MultipleColumns(String),
}

impl Column {
    /// Convert from command-line arguments to sequence of columns.
    ///
    /// The set of columns that will appear in the output table can be
    /// specified by command-line arguments. This function converts
    /// those arguments to a [`Vec`] of [`Column`] variants.
    ///
    /// # Errors
    ///
    /// This function returns an error if a column is specified more
    /// than once in the command-line argument.
    pub(crate) fn from_matches(matches: &ArgMatches) -> Result<Vec<Self>, ColumnError> {
        match (
            matches.is_present(OPT_PRINT_TYPE),
            matches.is_present(OPT_INODES),
            matches.occurrences_of(OPT_OUTPUT) > 0,
        ) {
            (false, false, false) => Ok(vec![
                Self::Source,
                Self::Size,
                Self::Used,
                Self::Avail,
                #[cfg(target_os = "macos")]
                Self::Capacity,
                Self::Pcent,
                Self::Target,
            ]),
            (false, false, true) => {
                // Unwrapping should not panic because in this arm of
                // the `match` statement, we know that `OPT_OUTPUT`
                // is non-empty.
                let names = matches.values_of(OPT_OUTPUT).unwrap();
                let mut seen: Vec<&str> = vec![];
                let mut columns = vec![];
                for name in names {
                    if seen.contains(&name) {
                        return Err(ColumnError::MultipleColumns(name.to_string()));
                    }
                    seen.push(name);
                    // Unwrapping here should not panic because the
                    // command-line argument parsing library should be
                    // responsible for ensuring each comma-separated
                    // string is a valid column label.
                    let column = Self::parse(name).unwrap();
                    columns.push(column);
                }
                Ok(columns)
            }
            (false, true, false) => Ok(vec![
                Self::Source,
                Self::Itotal,
                Self::Iused,
                Self::Iavail,
                Self::Ipcent,
                Self::Target,
            ]),
            (true, false, false) => Ok(vec![
                Self::Source,
                Self::Fstype,
                Self::Size,
                Self::Used,
                Self::Avail,
                #[cfg(target_os = "macos")]
                Self::Capacity,
                Self::Pcent,
                Self::Target,
            ]),
            (true, true, false) => Ok(vec![
                Self::Source,
                Self::Fstype,
                Self::Itotal,
                Self::Iused,
                Self::Iavail,
                Self::Ipcent,
                Self::Target,
            ]),
            // The command-line arguments -T and -i are each mutually
            // exclusive with --output, so the command-line argument
            // parser should reject those combinations before we get
            // to this point in the code.
            _ => unreachable!(),
        }
    }

    /// Convert a column name to the corresponding enumeration variant.
    ///
    /// There are twelve valid column names, one for each variant:
    ///
    /// - "source"
    /// - "fstype"
    /// - "itotal"
    /// - "iused"
    /// - "iavail"
    /// - "ipcent"
    /// - "size"
    /// - "used"
    /// - "avail"
    /// - "pcent"
    /// - "file"
    /// - "target"
    ///
    /// # Errors
    ///
    /// If the string `s` is not one of the valid column names.
    fn parse(s: &str) -> Result<Self, ()> {
        match s {
            "source" => Ok(Self::Source),
            "fstype" => Ok(Self::Fstype),
            "itotal" => Ok(Self::Itotal),
            "iused" => Ok(Self::Iused),
            "iavail" => Ok(Self::Iavail),
            "ipcent" => Ok(Self::Ipcent),
            "size" => Ok(Self::Size),
            "used" => Ok(Self::Used),
            "avail" => Ok(Self::Avail),
            "pcent" => Ok(Self::Pcent),
            "file" => Ok(Self::File),
            "target" => Ok(Self::Target),
            _ => Err(()),
        }
    }

    /// Return the alignment of the specified column.
    pub(crate) fn alignment(column: &Self) -> Alignment {
        match column {
            Self::Source | Self::Target | Self::File | Self::Fstype => Alignment::Left,
            _ => Alignment::Right,
        }
    }

    /// Return the minimum width of the specified column.
    pub(crate) fn min_width(column: &Self) -> usize {
        match column {
            // 14 = length of "Filesystem" plus 4 spaces
            Self::Source => 14,
            Self::Used => 5,
            // the shortest headers have a length of 4 chars so we use that as the minimum width
            _ => 4,
        }
    }
}

/// A column's alignment.
///
/// We define our own `Alignment` enum instead of using `std::fmt::Alignment` because df doesn't
/// have centered columns and hence a `Center` variant is not needed.
pub(crate) enum Alignment {
    Left,
    Right,
}
<|endoftext|>