<commit_before>//! Locale implementation using GNU libc

extern crate libc;

use ::std::sync::Arc;
use ::std::borrow::Cow;
use super::{LocaleFactory,Numeric,Time};

pub mod ffi;

/// Wrapper for libc's locale_t.
pub struct CLocale {
    c_locale: ffi::locale_t,
}

impl CLocale {
    /// Constructs new complete locale.
    ///
    /// Constructs `CLocale` with all categories from locale `locale`. See
    /// [`newlocale`](http://man7.org/linux/man-pages/man3/newlocale.3.html).
    pub fn new(locale: &str) -> Result<Self, i32> {
        let cloc = ::std::ffi::CString::new(locale);
        if cloc.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::newlocale(ffi::LC_ALL_MASK, cloc.unwrap().as_ptr(), ::std::ptr::null_mut()) };
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(CLocale { c_locale: res, })
        }
    }

    /// Constructs new complete locale.
    ///
    /// Constructs `CLocale` with specified categories from locale `locale` and the rest
    /// from `from`. `from` is destroyed in the process. See
    /// [`newlocale`(3)](http://man7.org/linux/man-pages/man3/newlocale.3.html).
    pub fn new_from(mask: libc::c_int, locale: &str, mut from: Self) -> Result<CLocale, i32> {
        let cloc = ::std::ffi::CString::new(locale);
        if cloc.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::newlocale(mask, cloc.unwrap().as_ptr(), from.c_locale) };
        // XXX: Is there better way to skip Drop then zeroing+check? And the associated need to
        // have the field mut though it's otherwise not needed and not desired?
        from.c_locale = ::std::ptr::null_mut();
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(CLocale { c_locale: res, })
        }
    }

    /// Returns locale data item.
    ///
    /// Returns given locale data item. Remember, that the result is encoded in the locale
    /// encoding, which may not be utf-8. To find what the locale charset is, query `langinfo` for
    /// `ffi::CODESET`. See
    /// [`nl_langinfo`(3)](http://man7.org/linux/man-pages/man3/nl_langinfo.3.html)
    pub fn langinfo<'a>(&'a self, item: libc::c_uint) -> &'a ::std::ffi::CStr {
        unsafe {
            let res = ffi::nl_langinfo_l(item, self.c_locale);
            ::std::ffi::CStr::from_ptr(res)
        }
    }
}

impl Drop for CLocale {
    fn drop(&mut self) {
        if !self.c_locale.is_null() {
            unsafe { ffi::freelocale(self.c_locale) };
        }
    }
}

impl Clone for CLocale {
    fn clone(&self) -> Self {
        CLocale {
            c_locale: unsafe { ffi::duplocale(self.c_locale) },
        }
    }
}

pub struct IConv {
    iconv: ffi::iconv_t,
}

/// Wrapper for iconv.
///
/// See [`iconv`(3)](http://man7.org/linux/man-pages/man3/iconv.3.html).
///
/// On Linux this is part of standard C library and should always be able to convert any charset
/// that the locale component presents, so we can conveniently use it for translating that to the
/// Rust's internal utf-8 encoding there.
impl IConv {
    /// Construct iconv converter.
    ///
    /// See [`iconv_open`(3)](http://man7.org/linux/man-pages/man3/iconv_open.3.html).
    pub fn new(to: &str, from: &str) -> Result<Self, i32> {
        let cto = ::std::ffi::CString::new(to);
        if cto.is_err() {
            return Err(libc::EINVAL);
        }
        let cfrom = ::std::ffi::CString::new(from);
        if cfrom.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::iconv_open(cto.unwrap().as_ptr(), cfrom.unwrap().as_ptr()) };
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(IConv { iconv: res, })
        }
    }

    /// Convert data with iconv
    ///
    /// See [`iconv`(3)](http://man7.org/linux/man-pages/man3/iconv.3.html). The parameters are
    ///
    ///  1. `src`: The input buffer.
    ///  2. `dst`: The output buffer.
    ///
    /// Return values are:
    ///
    ///  1. Result of `iconv`. If -1, the reason can be read from `std::os::errno()`.
    ///  2. Number of bytes processed from `src`.
    ///  3. Number of bytes written to `dst`.
    ///
    /// The C interface returns the remaining buffers instead, but that is actually hard to work
    /// with in Rust.
    pub fn convert(&self, src: &[u8], dst: &mut [u8]) -> (isize, usize, usize) {
        let mut inptr: *const libc::c_char = src.as_ptr() as *const libc::c_char;
        let mut insize: libc::size_t = src.len() as libc::size_t;
        let mut outptr: *mut libc::c_char = dst.as_ptr() as *mut libc::c_char;
        let mut outsize: libc::size_t = dst.len() as libc::size_t;
        // XXX: Do we need error handling? We don't expect errors and can't do much about them here.
        let res = unsafe {
            ffi::iconv(self.iconv,
                &mut inptr, &mut insize,
                &mut outptr, &mut outsize)
        };
        (res as isize, src.len() - (insize as usize), dst.len() - (outsize as usize))
    }
}

impl Drop for IConv {
    fn drop(&mut self) {
        if !self.iconv.is_null() {
            unsafe { ffi::iconv_close(self.iconv); }
        }
    }
}

#[derive(Clone)]
pub struct LibCLocaleFactory {
    locale: Arc<CLocale>,
    iconv: Option<Arc<IConv>>,
}

impl LibCLocaleFactory {
    // TODO TODO: Try to construct locale with CTYPE overriden to C.UTF-8 and check whether the
    // returned values now appear in UTF-8. Than we wouldn't need the conversion.
    // TODO TODO: Could also try overriding all components to their corresponding UTF-8 variants,
    // though that's quite a bit more work.
    pub fn new(locale: &str) -> Result<Self, i32> {
        let loc = Arc::new(try!(CLocale::new(locale)));
        let ccodeset = loc.langinfo(ffi::CODESET);
        let mut iconv = None;
        if let Ok(codeset) = ::std::str::from_utf8(ccodeset.to_bytes()) {
            if codeset != "UTF-8" {
                if let Ok(i) = IConv::new("UTF-8", codeset) {
                    iconv = Some(Arc::new(i));
                }
            }
        }
        return Ok(LibCLocaleFactory{
            locale: loc.clone(), // It says it's borrowed, but this is the last statement...
            iconv: iconv,
        });
    }

    pub fn langinfo<'a>(&'a self, item: libc::c_uint) -> Cow<'a, str> {
        let cres: &'a ::std::ffi::CStr = self.locale.langinfo(item);
        if let &Some(ref iconv) = &self.iconv {
            let mut buf = Vec::new();
            buf.resize(4 * cres.to_bytes().len(), 0u8);
            let conv = iconv.convert(cres.to_bytes(), &mut buf);
            buf.resize(conv.2, 0u8);
            if let Ok(s) = String::from_utf8(buf) {
                return Cow::Owned(s);
            }
        }
        return String::from_utf8_lossy(cres.to_bytes());
    }
}

impl LocaleFactory for LibCLocaleFactory {
    fn get_numeric(&mut self) -> Option<Box<Numeric>> {
        return Some(
            Box::new(
                Numeric::new(
                    &self.langinfo(ffi::RADIXCHAR),
                    &self.langinfo(ffi::THOUSEP))));
    }

    fn get_time(&mut self) -> Option<Box<Time>> {
        return Some(
            Box::new(
                Time {
                    month_names: vec![
                        self.langinfo(ffi::ABMON_1).into_owned(),
                        self.langinfo(ffi::ABMON_2).into_owned(),
                        self.langinfo(ffi::ABMON_3).into_owned(),
                        self.langinfo(ffi::ABMON_4).into_owned(),
                        self.langinfo(ffi::ABMON_5).into_owned(),
                        self.langinfo(ffi::ABMON_6).into_owned(),
                        self.langinfo(ffi::ABMON_7).into_owned(),
                        self.langinfo(ffi::ABMON_8).into_owned(),
                        self.langinfo(ffi::ABMON_9).into_owned(),
                        self.langinfo(ffi::ABMON_10).into_owned(),
                        self.langinfo(ffi::ABMON_11).into_owned(),
                        self.langinfo(ffi::ABMON_12).into_owned(),
                    ],
                    long_month_names: vec![
                        self.langinfo(ffi::MON_1).into_owned(),
                        self.langinfo(ffi::MON_2).into_owned(),
                        self.langinfo(ffi::MON_3).into_owned(),
                        self.langinfo(ffi::MON_4).into_owned(),
                        self.langinfo(ffi::MON_5).into_owned(),
                        self.langinfo(ffi::MON_6).into_owned(),
                        self.langinfo(ffi::MON_7).into_owned(),
                        self.langinfo(ffi::MON_8).into_owned(),
                        self.langinfo(ffi::MON_9).into_owned(),
                        self.langinfo(ffi::MON_10).into_owned(),
                        self.langinfo(ffi::MON_11).into_owned(),
                        self.langinfo(ffi::MON_12).into_owned(),
                    ],
                    day_names: vec![
                        self.langinfo(ffi::ABDAY_1).into_owned(),
                        self.langinfo(ffi::ABDAY_2).into_owned(),
                        self.langinfo(ffi::ABDAY_3).into_owned(),
                        self.langinfo(ffi::ABDAY_4).into_owned(),
                        self.langinfo(ffi::ABDAY_5).into_owned(),
                        self.langinfo(ffi::ABDAY_6).into_owned(),
                        self.langinfo(ffi::ABDAY_7).into_owned(),
                    ],
                    long_day_names: vec![
                        self.langinfo(ffi::DAY_1).into_owned(),
                        self.langinfo(ffi::DAY_2).into_owned(),
                        self.langinfo(ffi::DAY_3).into_owned(),
                        self.langinfo(ffi::DAY_4).into_owned(),
                        self.langinfo(ffi::DAY_5).into_owned(),
                        self.langinfo(ffi::DAY_6).into_owned(),
                        self.langinfo(ffi::DAY_7).into_owned(),
                    ],
                }));
    }
}

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

    // ---- tests for CLocale ----

    fn langinfo(loc: &CLocale, item: super::libc::c_uint) -> &str {
        let res = loc.langinfo(item);
        ::std::str::from_utf8(res.to_bytes()).unwrap()
    }

    #[test]
    fn c_locale() {
        let l = CLocale::new("C.UTF-8").unwrap();
        assert_eq!("UTF-8", langinfo(&l, ffi::CODESET));
    }

    #[test]
    fn en_locale() {
        let l = CLocale::new("en_GB").unwrap();
        assert_eq!("ISO-8859-1", langinfo(&l, ffi::CODESET));
    }

    #[test]
    fn bad_locale() {
        let l = CLocale::new("wrong");
        assert!(l.is_err());
    }

    #[test]
    fn mixed_locale() {
        let l = CLocale::new("cs_CZ").unwrap();
        assert_eq!(",", langinfo(&l, ffi::RADIXCHAR));
        assert_eq!("Po", langinfo(&l, ffi::ABDAY_2));
        let m = CLocale::new_from(ffi::LC_NUMERIC_MASK, "en_GB", l).unwrap();
        assert_eq!(".", langinfo(&m, ffi::RADIXCHAR));
        assert_eq!("Po", langinfo(&m, ffi::ABDAY_2));
        let n = CLocale::new_from(ffi::LC_TIME_MASK, "de_DE", m.clone()).unwrap();
        assert_eq!(".", langinfo(&n, ffi::RADIXCHAR));
        assert_eq!("Mi", langinfo(&n, ffi::ABDAY_4));
        assert_eq!(".", langinfo(&m, ffi::RADIXCHAR));
        assert_eq!("Po", langinfo(&m, ffi::ABDAY_2));
    }

    #[test]
    fn locale_with_convert() {
        let lf = LibCLocaleFactory::new("cs_CZ").unwrap();
        assert_eq!("ISO-8859-2", lf.langinfo(ffi::CODESET));
        assert_eq!("Út", lf.langinfo(ffi::ABDAY_3));
    }
}
<commit_msg>Skip tests for locales that the host does not support.<commit_after>//! Locale implementation using GNU libc

extern crate libc;

use ::std::sync::Arc;
use ::std::borrow::Cow;
use super::{LocaleFactory,Numeric,Time};

pub mod ffi;

/// Wrapper for libc's locale_t.
pub struct CLocale {
    c_locale: ffi::locale_t,
}

impl CLocale {
    /// Constructs new complete locale.
    ///
    /// Constructs `CLocale` with all categories from locale `locale`. See
    /// [`newlocale`](http://man7.org/linux/man-pages/man3/newlocale.3.html).
    pub fn new(locale: &str) -> Result<Self, i32> {
        let cloc = ::std::ffi::CString::new(locale);
        if cloc.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::newlocale(ffi::LC_ALL_MASK, cloc.unwrap().as_ptr(), ::std::ptr::null_mut()) };
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(CLocale { c_locale: res, })
        }
    }

    /// Constructs new complete locale.
    ///
    /// Constructs `CLocale` with specified categories from locale `locale` and the rest
    /// from `from`. `from` is destroyed in the process. See
    /// [`newlocale`(3)](http://man7.org/linux/man-pages/man3/newlocale.3.html).
    pub fn new_from(mask: libc::c_int, locale: &str, mut from: Self) -> Result<CLocale, i32> {
        let cloc = ::std::ffi::CString::new(locale);
        if cloc.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::newlocale(mask, cloc.unwrap().as_ptr(), from.c_locale) };
        // XXX: Is there better way to skip Drop then zeroing+check? And the associated need to
        // have the field mut though it's otherwise not needed and not desired?
        from.c_locale = ::std::ptr::null_mut();
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(CLocale { c_locale: res, })
        }
    }

    /// Returns locale data item.
    ///
    /// Returns given locale data item. Remember, that the result is encoded in the locale
    /// encoding, which may not be utf-8. To find what the locale charset is, query `langinfo` for
    /// `ffi::CODESET`. See
    /// [`nl_langinfo`(3)](http://man7.org/linux/man-pages/man3/nl_langinfo.3.html)
    pub fn langinfo<'a>(&'a self, item: libc::c_uint) -> &'a ::std::ffi::CStr {
        unsafe {
            let res = ffi::nl_langinfo_l(item, self.c_locale);
            ::std::ffi::CStr::from_ptr(res)
        }
    }
}

impl Drop for CLocale {
    fn drop(&mut self) {
        if !self.c_locale.is_null() {
            unsafe { ffi::freelocale(self.c_locale) };
        }
    }
}

impl Clone for CLocale {
    fn clone(&self) -> Self {
        CLocale {
            c_locale: unsafe { ffi::duplocale(self.c_locale) },
        }
    }
}

pub struct IConv {
    iconv: ffi::iconv_t,
}

/// Wrapper for iconv.
///
/// See [`iconv`(3)](http://man7.org/linux/man-pages/man3/iconv.3.html).
///
/// On Linux this is part of standard C library and should always be able to convert any charset
/// that the locale component presents, so we can conveniently use it for translating that to the
/// Rust's internal utf-8 encoding there.
impl IConv {
    /// Construct iconv converter.
    ///
    /// See [`iconv_open`(3)](http://man7.org/linux/man-pages/man3/iconv_open.3.html).
    pub fn new(to: &str, from: &str) -> Result<Self, i32> {
        let cto = ::std::ffi::CString::new(to);
        if cto.is_err() {
            return Err(libc::EINVAL);
        }
        let cfrom = ::std::ffi::CString::new(from);
        if cfrom.is_err() {
            return Err(libc::EINVAL);
        }
        let res = unsafe { ffi::iconv_open(cto.unwrap().as_ptr(), cfrom.unwrap().as_ptr()) };
        if res.is_null() {
            Err(::std::os::errno())
        } else {
            Ok(IConv { iconv: res, })
        }
    }

    /// Convert data with iconv
    ///
    /// See [`iconv`(3)](http://man7.org/linux/man-pages/man3/iconv.3.html). The parameters are
    ///
    ///  1. `src`: The input buffer.
    ///  2. `dst`: The output buffer.
    ///
    /// Return values are:
    ///
    ///  1. Result of `iconv`. If -1, the reason can be read from `std::os::errno()`.
    ///  2. Number of bytes processed from `src`.
    ///  3. Number of bytes written to `dst`.
    ///
    /// The C interface returns the remaining buffers instead, but that is actually hard to work
    /// with in Rust.
    pub fn convert(&self, src: &[u8], dst: &mut [u8]) -> (isize, usize, usize) {
        let mut inptr: *const libc::c_char = src.as_ptr() as *const libc::c_char;
        let mut insize: libc::size_t = src.len() as libc::size_t;
        let mut outptr: *mut libc::c_char = dst.as_ptr() as *mut libc::c_char;
        let mut outsize: libc::size_t = dst.len() as libc::size_t;
        // XXX: Do we need error handling? We don't expect errors and can't do much about them here.
        let res = unsafe {
            ffi::iconv(self.iconv,
                &mut inptr, &mut insize,
                &mut outptr, &mut outsize)
        };
        (res as isize, src.len() - (insize as usize), dst.len() - (outsize as usize))
    }
}

impl Drop for IConv {
    fn drop(&mut self) {
        if !self.iconv.is_null() {
            unsafe { ffi::iconv_close(self.iconv); }
        }
    }
}

#[derive(Clone)]
pub struct LibCLocaleFactory {
    locale: Arc<CLocale>,
    iconv: Option<Arc<IConv>>,
}

impl LibCLocaleFactory {
    // TODO TODO: Try to construct locale with CTYPE overriden to C.UTF-8 and check whether the
    // returned values now appear in UTF-8. Than we wouldn't need the conversion.
    // TODO TODO: Could also try overriding all components to their corresponding UTF-8 variants,
    // though that's quite a bit more work.
    pub fn new(locale: &str) -> Result<Self, i32> {
        let loc = Arc::new(try!(CLocale::new(locale)));
        let ccodeset = loc.langinfo(ffi::CODESET);
        let mut iconv = None;
        if let Ok(codeset) = ::std::str::from_utf8(ccodeset.to_bytes()) {
            if codeset != "UTF-8" {
                if let Ok(i) = IConv::new("UTF-8", codeset) {
                    iconv = Some(Arc::new(i));
                }
            }
        }
        return Ok(LibCLocaleFactory{
            locale: loc.clone(), // It says it's borrowed, but this is the last statement...
            iconv: iconv,
        });
    }

    pub fn langinfo<'a>(&'a self, item: libc::c_uint) -> Cow<'a, str> {
        let cres: &'a ::std::ffi::CStr = self.locale.langinfo(item);
        if let &Some(ref iconv) = &self.iconv {
            let mut buf = Vec::new();
            buf.resize(4 * cres.to_bytes().len(), 0u8);
            let conv = iconv.convert(cres.to_bytes(), &mut buf);
            buf.resize(conv.2, 0u8);
            if let Ok(s) = String::from_utf8(buf) {
                return Cow::Owned(s);
            }
        }
        return String::from_utf8_lossy(cres.to_bytes());
    }
}

impl LocaleFactory for LibCLocaleFactory {
    fn get_numeric(&mut self) -> Option<Box<Numeric>> {
        return Some(
            Box::new(
                Numeric::new(
                    &self.langinfo(ffi::RADIXCHAR),
                    &self.langinfo(ffi::THOUSEP))));
    }

    fn get_time(&mut self) -> Option<Box<Time>> {
        return Some(
            Box::new(
                Time {
                    month_names: vec![
                        self.langinfo(ffi::ABMON_1).into_owned(),
                        self.langinfo(ffi::ABMON_2).into_owned(),
                        self.langinfo(ffi::ABMON_3).into_owned(),
                        self.langinfo(ffi::ABMON_4).into_owned(),
                        self.langinfo(ffi::ABMON_5).into_owned(),
                        self.langinfo(ffi::ABMON_6).into_owned(),
                        self.langinfo(ffi::ABMON_7).into_owned(),
                        self.langinfo(ffi::ABMON_8).into_owned(),
                        self.langinfo(ffi::ABMON_9).into_owned(),
                        self.langinfo(ffi::ABMON_10).into_owned(),
                        self.langinfo(ffi::ABMON_11).into_owned(),
                        self.langinfo(ffi::ABMON_12).into_owned(),
                    ],
                    long_month_names: vec![
                        self.langinfo(ffi::MON_1).into_owned(),
                        self.langinfo(ffi::MON_2).into_owned(),
                        self.langinfo(ffi::MON_3).into_owned(),
                        self.langinfo(ffi::MON_4).into_owned(),
                        self.langinfo(ffi::MON_5).into_owned(),
                        self.langinfo(ffi::MON_6).into_owned(),
                        self.langinfo(ffi::MON_7).into_owned(),
                        self.langinfo(ffi::MON_8).into_owned(),
                        self.langinfo(ffi::MON_9).into_owned(),
                        self.langinfo(ffi::MON_10).into_owned(),
                        self.langinfo(ffi::MON_11).into_owned(),
                        self.langinfo(ffi::MON_12).into_owned(),
                    ],
                    day_names: vec![
                        self.langinfo(ffi::ABDAY_1).into_owned(),
                        self.langinfo(ffi::ABDAY_2).into_owned(),
                        self.langinfo(ffi::ABDAY_3).into_owned(),
                        self.langinfo(ffi::ABDAY_4).into_owned(),
                        self.langinfo(ffi::ABDAY_5).into_owned(),
                        self.langinfo(ffi::ABDAY_6).into_owned(),
                        self.langinfo(ffi::ABDAY_7).into_owned(),
                    ],
                    long_day_names: vec![
                        self.langinfo(ffi::DAY_1).into_owned(),
                        self.langinfo(ffi::DAY_2).into_owned(),
                        self.langinfo(ffi::DAY_3).into_owned(),
                        self.langinfo(ffi::DAY_4).into_owned(),
                        self.langinfo(ffi::DAY_5).into_owned(),
                        self.langinfo(ffi::DAY_6).into_owned(),
                        self.langinfo(ffi::DAY_7).into_owned(),
                    ],
                }));
    }
}

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

    // ---- tests for CLocale ----

    fn langinfo(loc: &CLocale, item: super::libc::c_uint) -> &str {
        let res = loc.langinfo(item);
        ::std::str::from_utf8(res.to_bytes()).unwrap()
    }

    #[test]
    fn c_locale() {
        let l = CLocale::new("C.UTF-8").unwrap();
        assert_eq!("UTF-8", langinfo(&l, ffi::CODESET));
    }

    #[test]
    fn en_locale() {
        if let Ok(l) = CLocale::new("en_GB") {
            // only test if the host has cs_CZ (non-unicode) locale (travis boxen don't)
            assert_eq!("ISO-8859-1", langinfo(&l, ffi::CODESET));
        }
    }

    #[test]
    fn bad_locale() {
        let l = CLocale::new("wrong");
        assert!(l.is_err());
    }

    #[test]
    fn mixed_locale() {
        if let Ok(l) = CLocale::new("cs_CZ") {
            // only test if the host has these locales (travis boxen don't)
            assert_eq!(",", langinfo(&l, ffi::RADIXCHAR));
            assert_eq!("Po", langinfo(&l, ffi::ABDAY_2));
            if let Ok(m) = CLocale::new_from(ffi::LC_NUMERIC_MASK, "en_GB", l) {
                assert_eq!(".", langinfo(&m, ffi::RADIXCHAR));
                assert_eq!("Po", langinfo(&m, ffi::ABDAY_2));
                if let Ok(n) = CLocale::new_from(ffi::LC_TIME_MASK, "de_DE", m.clone()) {
                    assert_eq!(".", langinfo(&n, ffi::RADIXCHAR));
                    assert_eq!("Mi", langinfo(&n, ffi::ABDAY_4));
                    assert_eq!(".", langinfo(&m, ffi::RADIXCHAR));
                    assert_eq!("Po", langinfo(&m, ffi::ABDAY_2));
                }
            }
        }
    }

    #[test]
    fn locale_with_convert() {
        if let Ok(lf) = LibCLocaleFactory::new("cs_CZ") {
            // only test if the host has cs_CZ (non-unicode) locale (travis boxen don't)
            assert_eq!("ISO-8859-2", lf.langinfo(ffi::CODESET));
            assert_eq!("Út", lf.langinfo(ffi::ABDAY_3));
        }
    }
}
<|endoftext|>


<commit_before>use std::sync::mpsc::channel;
use std::thread;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::ops::Deref;

use rvi;
use handler::ServiceHandler;
use message::{InitiateParams, BackendServices, PackageId};
use message::{Notification, ServerPackageReport, LocalServices, ServerReport};
use configuration::Configuration;
use persistence::Transfer;
use sota_dbus;

/// Start a SOTA client service with the provided configuration
pub fn start(conf: &Configuration, rvi_url: String, edge_url: String) {
    // will receive RVI registration details
    let (tx_edge, rx_edge) = channel();
    let rvi_edge = rvi::ServiceEdge::new(rvi_url.clone(),
                                         edge_url.clone(),
                                         tx_edge);

    let transfers: Arc<Mutex<HashMap<PackageId, Transfer>>> =
        Arc::new(Mutex::new(HashMap::new()));

    // will receive notifies from RVI and install requests from dbus
    let (tx_main, rx_main) = channel();
    let handler = ServiceHandler::new(transfers.clone(), tx_main.clone(),
                                      rvi_url.clone(), conf.clone());

    match conf.client.timeout {
        Some(timeout) => {
            let _ = thread::spawn(move || {
                ServiceHandler::start_timer(transfers.deref(), timeout);
            });
        },
        None => info!("No timeout configured, transfers will never time out.")
    }

    let services = vec!("/sota/notify",
                        "/sota/start",
                        "/sota/chunk",
                        "/sota/finish",
                        "/sota/getpackages",
                        "/sota/abort");

    thread::spawn(move || {
        rvi_edge.start(handler, services);
    });

    let dbus_receiver = sota_dbus::Receiver::new(conf.dbus.clone(),
                                                 tx_main.clone());
    thread::spawn(move || {
        dbus_receiver.start();
    });

    let local_services = LocalServices::new(&rx_edge.recv().unwrap());
    let mut backend_services = BackendServices::new();

    loop {
        match rx_main.recv().unwrap() {
            Notification::Notify(notify) => {
                backend_services.update(&notify.services);
                sota_dbus::send_notify(&conf.dbus, notify.packages);
            },
            Notification::Initiate(packages) => {
                let initiate =
                    InitiateParams::new(packages, local_services.clone(),
                                        local_services
                                        .get_vin(conf.client.vin_match));
                match rvi::send_message(&rvi_url, initiate,
                                        &backend_services.start) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't initiate download: {}", e)
                }
            },
            Notification::Finish(package) => {
                let report = sota_dbus::request_install(&conf.dbus, package);
                let server_report =
                    ServerPackageReport::new(report, local_services
                                             .get_vin(conf.client.vin_match));

                match rvi::send_message(&rvi_url, server_report,
                                        &backend_services.packages) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't send report: {}", e)
                }
            },
            Notification::Report => {
                let packages = sota_dbus::request_report(&conf.dbus);
                let report =
                    ServerReport::new(packages, local_services
                                      .get_vin(conf.client.vin_match));

                match rvi::send_message(&rvi_url, report,
                                        &backend_services.packages) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't send report: {}", e)
                }
            }
        }
    }
}
<commit_msg>Fix report endpoint<commit_after>use std::sync::mpsc::channel;
use std::thread;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::ops::Deref;

use rvi;
use handler::ServiceHandler;
use message::{InitiateParams, BackendServices, PackageId};
use message::{Notification, ServerPackageReport, LocalServices, ServerReport};
use configuration::Configuration;
use persistence::Transfer;
use sota_dbus;

/// Start a SOTA client service with the provided configuration
pub fn start(conf: &Configuration, rvi_url: String, edge_url: String) {
    // will receive RVI registration details
    let (tx_edge, rx_edge) = channel();
    let rvi_edge = rvi::ServiceEdge::new(rvi_url.clone(),
                                         edge_url.clone(),
                                         tx_edge);

    let transfers: Arc<Mutex<HashMap<PackageId, Transfer>>> =
        Arc::new(Mutex::new(HashMap::new()));

    // will receive notifies from RVI and install requests from dbus
    let (tx_main, rx_main) = channel();
    let handler = ServiceHandler::new(transfers.clone(), tx_main.clone(),
                                      rvi_url.clone(), conf.clone());

    match conf.client.timeout {
        Some(timeout) => {
            let _ = thread::spawn(move || {
                ServiceHandler::start_timer(transfers.deref(), timeout);
            });
        },
        None => info!("No timeout configured, transfers will never time out.")
    }

    let services = vec!("/sota/notify",
                        "/sota/start",
                        "/sota/chunk",
                        "/sota/finish",
                        "/sota/getpackages",
                        "/sota/abort");

    thread::spawn(move || {
        rvi_edge.start(handler, services);
    });

    let dbus_receiver = sota_dbus::Receiver::new(conf.dbus.clone(),
                                                 tx_main.clone());
    thread::spawn(move || {
        dbus_receiver.start();
    });

    let local_services = LocalServices::new(&rx_edge.recv().unwrap());
    let mut backend_services = BackendServices::new();

    loop {
        match rx_main.recv().unwrap() {
            Notification::Notify(notify) => {
                backend_services.update(&notify.services);
                sota_dbus::send_notify(&conf.dbus, notify.packages);
            },
            Notification::Initiate(packages) => {
                let initiate =
                    InitiateParams::new(packages, local_services.clone(),
                                        local_services
                                        .get_vin(conf.client.vin_match));
                match rvi::send_message(&rvi_url, initiate,
                                        &backend_services.start) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't initiate download: {}", e)
                }
            },
            Notification::Finish(package) => {
                let report = sota_dbus::request_install(&conf.dbus, package);
                let server_report =
                    ServerPackageReport::new(report, local_services
                                             .get_vin(conf.client.vin_match));

                match rvi::send_message(&rvi_url, server_report,
                                        &backend_services.report) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't send report: {}", e)
                }
            },
            Notification::Report => {
                let packages = sota_dbus::request_report(&conf.dbus);
                let report =
                    ServerReport::new(packages, local_services
                                      .get_vin(conf.client.vin_match));

                match rvi::send_message(&rvi_url, report,
                                        &backend_services.packages) {
                    Ok(..) => {},
                    Err(e) => error!("Couldn't send report: {}", e)
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use std::{
    fs::File,
    io::{Cursor, Read, Seek, SeekFrom},
    path::{Path, PathBuf},
};

use crate::common::pak::Pak;

use failure::Error;

enum VfsComponent {
    Pak(Pak),
    Directory(PathBuf),
}

pub struct Vfs {
    components: Vec<VfsComponent>,
}

impl Vfs {
    pub fn new() -> Vfs {
        Vfs {
            components: Vec::new(),
        }
    }

    pub fn add_pakfile<P>(&mut self, path: P) -> Result<(), Error>
    where
        P: AsRef<Path>,
    {
        self.components.push(VfsComponent::Pak(Pak::new(path)?));

        Ok(())
    }

    pub fn add_directory<P>(&mut self, path: P) -> Result<(), Error>
    where
        P: AsRef<Path>,
    {
        self.components
            .push(VfsComponent::Directory(path.as_ref().to_path_buf()));

        Ok(())
    }

    pub fn open<S>(&self, virtual_path: S) -> Result<VirtualFile, Error>
    where
        S: AsRef<str>,
    {
        for c in self.components.iter().rev() {
            let vp = virtual_path.as_ref();

            match c {
                VfsComponent::Pak(pak) => {
                    if let Ok(f) = pak.open(vp) {
                        return Ok(VirtualFile::PakBacked(Cursor::new(f)));
                    }
                }

                VfsComponent::Directory(path) => {
                    let mut full_path = path.to_owned();
                    full_path.push(vp);

                    if let Ok(f) = File::open(full_path) {
                        return Ok(VirtualFile::FileBacked(f));
                    }
                }
            }
        }

        bail!("File not found.");
    }
}

pub enum VirtualFile<'a> {
    PakBacked(Cursor<&'a [u8]>),
    FileBacked(File),
}

impl<'a> Read for VirtualFile<'a> {
    fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result<usize> {
        match self {
            VirtualFile::PakBacked(data) => data.read(buf),
            VirtualFile::FileBacked(file) => file.read(buf),
        }
    }
}

impl<'a> Seek for VirtualFile<'a> {
    fn seek(&mut self, pos: SeekFrom) -> ::std::io::Result<u64> {
        match self {
            VirtualFile::PakBacked(data) => data.seek(pos),
            VirtualFile::FileBacked(file) => file.seek(pos),
        }
    }
}
<commit_msg>Add VfsError type<commit_after>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use std::{
    convert::From,
    fmt::{self, Display},
    fs::File,
    io::{Cursor, Read, Seek, SeekFrom},
    path::{Path, PathBuf},
};

use crate::common::pak::Pak;

use failure::{Backtrace, Context, Error, Fail, ResultExt};

#[derive(Debug)]
pub struct VfsError {
    inner: Context<VfsErrorKind>,
}

impl VfsError {
    pub fn kind(&self) -> VfsErrorKind {
        self.inner.get_context().clone()
    }
}

impl From<VfsErrorKind> for VfsError {
    fn from(kind: VfsErrorKind) -> Self {
        VfsError {
            inner: Context::new(kind),
        }
    }
}

impl From<Context<VfsErrorKind>> for VfsError {
    fn from(inner: Context<VfsErrorKind>) -> Self {
        VfsError { inner }
    }
}

impl Fail for VfsError {
    fn cause(&self) -> Option<&dyn Fail> {
        self.inner.cause()
    }

    fn backtrace(&self) -> Option<&Backtrace> {
        self.inner.backtrace()
    }
}

impl Display for VfsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.inner, f)
    }
}

#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum VfsErrorKind {
    #[fail(display = "Couldn't load pakfile: {}", path)]
    PakfileNotLoaded { path: String },
    #[fail(display = "File does not exist: {}", path)]
    NoSuchFile { path: String },
}

enum VfsComponent {
    Pak(Pak),
    Directory(PathBuf),
}

pub struct Vfs {
    components: Vec<VfsComponent>,
}

impl Vfs {
    pub fn new() -> Vfs {
        Vfs {
            components: Vec::new(),
        }
    }

    pub fn add_pakfile<P>(&mut self, path: P) -> Result<(), VfsError>
    where
        P: AsRef<Path>,
    {
        let path = path.as_ref();

        self.components
            .push(VfsComponent::Pak(Pak::new(path).context(
                VfsErrorKind::PakfileNotLoaded {
                    path: path.to_string_lossy().into_owned(),
                },
            )?));

        Ok(())
    }

    pub fn add_directory<P>(&mut self, path: P) -> Result<(), VfsError>
    where
        P: AsRef<Path>,
    {
        self.components
            .push(VfsComponent::Directory(path.as_ref().to_path_buf()));

        Ok(())
    }

    pub fn open<S>(&self, virtual_path: S) -> Result<VirtualFile, VfsError>
    where
        S: AsRef<str>,
    {
        let vp = virtual_path.as_ref();

        // iterate in reverse so later PAKs overwrite earlier ones
        for c in self.components.iter().rev() {

            match c {
                VfsComponent::Pak(pak) => {
                    if let Ok(f) = pak.open(vp) {
                        return Ok(VirtualFile::PakBacked(Cursor::new(f)));
                    }
                }

                VfsComponent::Directory(path) => {
                    let mut full_path = path.to_owned();
                    full_path.push(vp);

                    if let Ok(f) = File::open(full_path) {
                        return Ok(VirtualFile::FileBacked(f));
                    }
                }
            }
        }

        Err(VfsErrorKind::NoSuchFile { path: vp.to_owned() })?
    }
}

pub enum VirtualFile<'a> {
    PakBacked(Cursor<&'a [u8]>),
    FileBacked(File),
}

impl<'a> Read for VirtualFile<'a> {
    fn read(&mut self, buf: &mut [u8]) -> ::std::io::Result<usize> {
        match self {
            VirtualFile::PakBacked(data) => data.read(buf),
            VirtualFile::FileBacked(file) => file.read(buf),
        }
    }
}

impl<'a> Seek for VirtualFile<'a> {
    fn seek(&mut self, pos: SeekFrom) -> ::std::io::Result<u64> {
        match self {
            VirtualFile::PakBacked(data) => data.seek(pos),
            VirtualFile::FileBacked(file) => file.seek(pos),
        }
    }
}
<|endoftext|>


<commit_before>//! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
//! case of the source (e.g. `my-field`, `MY_FIELD`).

// See https://users.rust-lang.org/t/psa-dealing-with-warning-unused-import-std-ascii-asciiext-in-today-s-nightly/13726
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;

use std::fmt::{self, Display};

use self::RenameRule::*;

/// The different possible ways to change case of fields in a struct, or variants in an enum.
#[derive(Copy, Clone, PartialEq)]
pub enum RenameRule {
    /// Don't apply a default rename rule.
    None,
    /// Rename direct children to "lowercase" style.
    LowerCase,
    /// Rename direct children to "UPPERCASE" style.
    UPPERCASE,
    /// Rename direct children to "PascalCase" style, as typically used for
    /// enum variants.
    PascalCase,
    /// Rename direct children to "camelCase" style.
    CamelCase,
    /// Rename direct children to "snake_case" style, as commonly used for
    /// fields.
    SnakeCase,
    /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
    /// used for constants.
    ScreamingSnakeCase,
    /// Rename direct children to "kebab-case" style.
    KebabCase,
    /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
    ScreamingKebabCase,
}

impl RenameRule {
    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
        match rename_all_str {
            "lowercase" => Ok(LowerCase),
            "UPPERCASE" => Ok(UPPERCASE),
            "PascalCase" => Ok(PascalCase),
            "camelCase" => Ok(CamelCase),
            "snake_case" => Ok(SnakeCase),
            "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase),
            "kebab-case" => Ok(KebabCase),
            "SCREAMING-KEBAB-CASE" => Ok(ScreamingKebabCase),
            unknown => Err(ParseError { unknown }),
        }
    }

    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
    pub fn apply_to_variant(&self, variant: &str) -> String {
        match *self {
            None | PascalCase => variant.to_owned(),
            LowerCase => variant.to_ascii_lowercase(),
            UPPERCASE => variant.to_ascii_uppercase(),
            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
            SnakeCase => {
                let mut snake = String::new();
                for (i, ch) in variant.char_indices() {
                    if i > 0 && ch.is_uppercase() {
                        snake.push('_');
                    }
                    snake.push(ch.to_ascii_lowercase());
                }
                snake
            }
            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
            ScreamingKebabCase => ScreamingSnakeCase
                .apply_to_variant(variant)
                .replace('_', "-"),
        }
    }

    /// Apply a renaming rule to a struct field, returning the version expected in the source.
    pub fn apply_to_field(&self, field: &str) -> String {
        match *self {
            None | LowerCase | SnakeCase => field.to_owned(),
            UPPERCASE => field.to_ascii_uppercase(),
            PascalCase => {
                let mut pascal = String::new();
                let mut capitalize = true;
                for ch in field.chars() {
                    if ch == '_' {
                        capitalize = true;
                    } else if capitalize {
                        pascal.push(ch.to_ascii_uppercase());
                        capitalize = false;
                    } else {
                        pascal.push(ch);
                    }
                }
                pascal
            }
            CamelCase => {
                let pascal = PascalCase.apply_to_field(field);
                pascal[..1].to_ascii_lowercase() + &pascal[1..]
            }
            ScreamingSnakeCase => field.to_ascii_uppercase(),
            KebabCase => field.replace('_', "-"),
            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
        }
    }
}

pub struct ParseError<'a> {
    unknown: &'a str,
}

impl<'a> Display for ParseError<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "unknown rename rule for #[serde(rename_all = {:?})]",
            self.unknown,
        )
    }
}

#[test]
fn rename_variants() {
    for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
        (
            "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
        ),
        (
            "VeryTasty",
            "verytasty",
            "VERYTASTY",
            "veryTasty",
            "very_tasty",
            "VERY_TASTY",
            "very-tasty",
            "VERY-TASTY",
        ),
        ("A", "a", "A", "a", "a", "A", "a", "A"),
        ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
    ] {
        assert_eq!(None.apply_to_variant(original), original);
        assert_eq!(LowerCase.apply_to_variant(original), lower);
        assert_eq!(UPPERCASE.apply_to_variant(original), upper);
        assert_eq!(PascalCase.apply_to_variant(original), original);
        assert_eq!(CamelCase.apply_to_variant(original), camel);
        assert_eq!(SnakeCase.apply_to_variant(original), snake);
        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
        assert_eq!(KebabCase.apply_to_variant(original), kebab);
        assert_eq!(
            ScreamingKebabCase.apply_to_variant(original),
            screaming_kebab
        );
    }
}

#[test]
fn rename_fields() {
    for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
        (
            "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
        ),
        (
            "very_tasty",
            "VERY_TASTY",
            "VeryTasty",
            "veryTasty",
            "VERY_TASTY",
            "very-tasty",
            "VERY-TASTY",
        ),
        ("a", "A", "A", "a", "A", "a", "A"),
        ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
    ] {
        assert_eq!(None.apply_to_field(original), original);
        assert_eq!(UPPERCASE.apply_to_field(original), upper);
        assert_eq!(PascalCase.apply_to_field(original), pascal);
        assert_eq!(CamelCase.apply_to_field(original), camel);
        assert_eq!(SnakeCase.apply_to_field(original), original);
        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
        assert_eq!(KebabCase.apply_to_field(original), kebab);
        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
    }
}
<commit_msg>Parse rename rules based on table of rules<commit_after>//! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
//! case of the source (e.g. `my-field`, `MY_FIELD`).

// See https://users.rust-lang.org/t/psa-dealing-with-warning-unused-import-std-ascii-asciiext-in-today-s-nightly/13726
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;

use std::fmt::{self, Display};

use self::RenameRule::*;

/// The different possible ways to change case of fields in a struct, or variants in an enum.
#[derive(Copy, Clone, PartialEq)]
pub enum RenameRule {
    /// Don't apply a default rename rule.
    None,
    /// Rename direct children to "lowercase" style.
    LowerCase,
    /// Rename direct children to "UPPERCASE" style.
    UPPERCASE,
    /// Rename direct children to "PascalCase" style, as typically used for
    /// enum variants.
    PascalCase,
    /// Rename direct children to "camelCase" style.
    CamelCase,
    /// Rename direct children to "snake_case" style, as commonly used for
    /// fields.
    SnakeCase,
    /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
    /// used for constants.
    ScreamingSnakeCase,
    /// Rename direct children to "kebab-case" style.
    KebabCase,
    /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
    ScreamingKebabCase,
}

static RENAME_RULES: &[(&str, RenameRule)] = &[
    ("lowercase", LowerCase),
    ("UPPERCASE", UPPERCASE),
    ("PascalCase", PascalCase),
    ("camelCase", CamelCase),
    ("snake_case", SnakeCase),
    ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
    ("kebab-case", KebabCase),
    ("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
];

impl RenameRule {
    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
        for (name, rule) in RENAME_RULES {
            if rename_all_str == *name {
                return Ok(*rule);
            }
        }
        Err(ParseError {
            unknown: rename_all_str,
        })
    }

    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
    pub fn apply_to_variant(&self, variant: &str) -> String {
        match *self {
            None | PascalCase => variant.to_owned(),
            LowerCase => variant.to_ascii_lowercase(),
            UPPERCASE => variant.to_ascii_uppercase(),
            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
            SnakeCase => {
                let mut snake = String::new();
                for (i, ch) in variant.char_indices() {
                    if i > 0 && ch.is_uppercase() {
                        snake.push('_');
                    }
                    snake.push(ch.to_ascii_lowercase());
                }
                snake
            }
            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
            ScreamingKebabCase => ScreamingSnakeCase
                .apply_to_variant(variant)
                .replace('_', "-"),
        }
    }

    /// Apply a renaming rule to a struct field, returning the version expected in the source.
    pub fn apply_to_field(&self, field: &str) -> String {
        match *self {
            None | LowerCase | SnakeCase => field.to_owned(),
            UPPERCASE => field.to_ascii_uppercase(),
            PascalCase => {
                let mut pascal = String::new();
                let mut capitalize = true;
                for ch in field.chars() {
                    if ch == '_' {
                        capitalize = true;
                    } else if capitalize {
                        pascal.push(ch.to_ascii_uppercase());
                        capitalize = false;
                    } else {
                        pascal.push(ch);
                    }
                }
                pascal
            }
            CamelCase => {
                let pascal = PascalCase.apply_to_field(field);
                pascal[..1].to_ascii_lowercase() + &pascal[1..]
            }
            ScreamingSnakeCase => field.to_ascii_uppercase(),
            KebabCase => field.replace('_', "-"),
            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
        }
    }
}

pub struct ParseError<'a> {
    unknown: &'a str,
}

impl<'a> Display for ParseError<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "unknown rename rule for #[serde(rename_all = {:?})]",
            self.unknown,
        )
    }
}

#[test]
fn rename_variants() {
    for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
        (
            "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
        ),
        (
            "VeryTasty",
            "verytasty",
            "VERYTASTY",
            "veryTasty",
            "very_tasty",
            "VERY_TASTY",
            "very-tasty",
            "VERY-TASTY",
        ),
        ("A", "a", "A", "a", "a", "A", "a", "A"),
        ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
    ] {
        assert_eq!(None.apply_to_variant(original), original);
        assert_eq!(LowerCase.apply_to_variant(original), lower);
        assert_eq!(UPPERCASE.apply_to_variant(original), upper);
        assert_eq!(PascalCase.apply_to_variant(original), original);
        assert_eq!(CamelCase.apply_to_variant(original), camel);
        assert_eq!(SnakeCase.apply_to_variant(original), snake);
        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
        assert_eq!(KebabCase.apply_to_variant(original), kebab);
        assert_eq!(
            ScreamingKebabCase.apply_to_variant(original),
            screaming_kebab
        );
    }
}

#[test]
fn rename_fields() {
    for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
        (
            "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
        ),
        (
            "very_tasty",
            "VERY_TASTY",
            "VeryTasty",
            "veryTasty",
            "VERY_TASTY",
            "very-tasty",
            "VERY-TASTY",
        ),
        ("a", "A", "A", "a", "A", "a", "A"),
        ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
    ] {
        assert_eq!(None.apply_to_field(original), original);
        assert_eq!(UPPERCASE.apply_to_field(original), upper);
        assert_eq!(PascalCase.apply_to_field(original), pascal);
        assert_eq!(CamelCase.apply_to_field(original), camel);
        assert_eq!(SnakeCase.apply_to_field(original), original);
        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
        assert_eq!(KebabCase.apply_to_field(original), kebab);
        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
    }
}
<|endoftext|>


<commit_before>extern crate bear_lib_terminal;

use ::backend;
use ::event::{Event, Key};
use self::bear_lib_terminal::Color as BltColor;
use self::bear_lib_terminal::geometry::{Point, Size};
use self::bear_lib_terminal::terminal::{self, Event as BltEvent, KeyCode};
use std::collections::BTreeMap;
use std::io::Write;
use ::theme::{BaseColor, Color, ColorStyle, Effect};

pub struct Concrete {
    colours: BTreeMap<i16, (BltColor, BltColor)>,
}

impl backend::Backend for Concrete {
    fn init() -> Self {
        // TODO: Make this configurable?
        terminal::open("Cursive", 80, 80);

        Concrete { colours: BTreeMap::new() }
    }

    fn finish(&mut self) {
        terminal::close();
    }

    fn init_color_style(&mut self, style: ColorStyle, foreground: &Color,
                        background: &Color) {
        self.colours.insert(style.id(),
                            (colour_to_blt_colour(foreground),
                             colour_to_blt_colour(background)));
    }

    fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {
        let (fg, bg) = self.colours[&color.id()];
        terminal::with_colors(fg, bg, f);
    }

    fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
        match effect {
            Effect::Simple => f(),
            // TODO: how to do this correctly?`
            //       BLT itself doesn't do this kind of thing,
            //       we'd need the colours in our position,
            //       but `f()` can do whatever
            Effect::Reverse => {
                terminal::with_colors(BltColor::from_rgb(0, 0, 0),
                                      BltColor::from_rgb(255, 255, 255),
                                      f)
            }
        }
    }

    fn has_colors(&self) -> bool {
        true
    }

    fn screen_size(&self) -> (usize, usize) {
        let Size { width, height } = terminal::state::size();
        (width as usize, height as usize)
    }

    fn clear(&self) {
        terminal::clear(None);
    }

    fn refresh(&mut self) {
        terminal::refresh();
    }

    fn print_at(&self, (x, y): (usize, usize), text: &str) {
        terminal::print_xy(x as i32, y as i32, text);
    }

    fn set_refresh_rate(&mut self, fps: u32) {
        // TODO: unsupported
    }

    fn poll_event(&self) -> Event {
        if let Some(ev) = terminal::wait_event() {
            match ev {
                // TODO: what should we do here?
                BltEvent::Close => Event::Exit,
                BltEvent::Resize { .. } => Event::WindowResize,
                // TODO: mouse support
                BltEvent::MouseMove { .. } => Event::Refresh,
                BltEvent::MouseScroll { .. } => Event::Refresh,
                BltEvent::KeyPressed { key, ctrl, shift } => {
                    blt_keycode_to_ev(key, shift, ctrl)
                }
                // TODO: there's no Key::Shift/Ctrl for w/e reason
                BltEvent::ShiftPressed => Event::Refresh,
                BltEvent::ControlPressed => Event::Refresh,
                // TODO: what should we do here?
                BltEvent::KeyReleased { .. } |
                BltEvent::ShiftReleased |
                BltEvent::ControlReleased => Event::Refresh,
            }
        } else {
            Event::Refresh
        }
    }
}

fn colour_to_blt_colour(clr: &Color) -> BltColor {
    let (r, g, b) = match *clr {
        // Colours taken from
        // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
        Color::Dark(BaseColor::Black) => (0, 0, 0),
        Color::Dark(BaseColor::Red) => (170, 0, 0),
        Color::Dark(BaseColor::Green) => (0, 170, 0),
        Color::Dark(BaseColor::Yellow) => (170, 85, 0),
        Color::Dark(BaseColor::Blue) => (0, 0, 170),
        Color::Dark(BaseColor::Magenta) => (170, 0, 170),
        Color::Dark(BaseColor::Cyan) => (0, 170, 170),
        Color::Dark(BaseColor::White) => (170, 170, 170),

        Color::Light(BaseColor::Black) => (85, 85, 85),
        Color::Light(BaseColor::Red) => (255, 85, 85),
        Color::Light(BaseColor::Green) => (85, 255, 85),
        Color::Light(BaseColor::Yellow) => (255, 255, 85),
        Color::Light(BaseColor::Blue) => (85, 85, 255),
        Color::Light(BaseColor::Magenta) => (255, 85, 255),
        Color::Light(BaseColor::Cyan) => (85, 255, 255),
        Color::Light(BaseColor::White) => (255, 255, 255),

        Color::Rgb(r, g, b) => (r, g, b),
        Color::RgbLowRes(r, g, b) => {
            ((r as f32 / 5.0 * 255.0) as u8,
             (g as f32 / 5.0 * 255.0) as u8,
             (b as f32 / 5.0 * 255.0) as u8)
        }
    };
    BltColor::from_rgb(r, g, b)
}

fn blt_keycode_to_ev(kc: KeyCode, shift: bool, ctrl: bool) -> Event {
    match kc {
        KeyCode::F1 | KeyCode::F2 | KeyCode::F3 | KeyCode::F4 |
        KeyCode::F5 | KeyCode::F6 | KeyCode::F7 | KeyCode::F8 |
        KeyCode::F9 | KeyCode::F10 | KeyCode::F11 | KeyCode::F12 |
        KeyCode::Enter | KeyCode::Escape | KeyCode::Backspace |
        KeyCode::Tab | KeyCode::Pause | KeyCode::Insert | KeyCode::Home |
        KeyCode::PageUp | KeyCode::Delete | KeyCode::End |
        KeyCode::PageDown | KeyCode::Right | KeyCode::Left |
        KeyCode::Down | KeyCode::Up => {
            match (shift, ctrl) {
                (true, true) => Event::CtrlShift(blt_keycode_to_key(kc)),
                (true, false) => Event::Shift(blt_keycode_to_key(kc)),
                (false, true) => Event::Ctrl(blt_keycode_to_key(kc)),
                (false, false) => Event::Key(blt_keycode_to_key(kc)),
            }
        }
        // TODO: mouse support
        KeyCode::MouseLeft | KeyCode::MouseRight | KeyCode::MouseMiddle |
        KeyCode::MouseFourth | KeyCode::MouseFifth => Event::Refresh,
        KeyCode::A | KeyCode::B | KeyCode::C | KeyCode::D | KeyCode::E |
        KeyCode::F | KeyCode::G | KeyCode::H | KeyCode::I | KeyCode::J |
        KeyCode::K | KeyCode::L | KeyCode::M | KeyCode::N | KeyCode::O |
        KeyCode::P | KeyCode::Q | KeyCode::R | KeyCode::S | KeyCode::T |
        KeyCode::U | KeyCode::V | KeyCode::W | KeyCode::X | KeyCode::Y |
        KeyCode::Z | KeyCode::Row1 | KeyCode::Row2 | KeyCode::Row3 |
        KeyCode::Row4 | KeyCode::Row5 | KeyCode::Row6 | KeyCode::Row7 |
        KeyCode::Row8 | KeyCode::Row9 | KeyCode::Row0 | KeyCode::Grave |
        KeyCode::Minus | KeyCode::Equals | KeyCode::LeftBracket |
        KeyCode::RightBracket | KeyCode::Backslash | KeyCode::Semicolon |
        KeyCode::Apostrophe | KeyCode::Comma | KeyCode::Period |
        KeyCode::Slash | KeyCode::Space | KeyCode::NumDivide |
        KeyCode::NumMultiply | KeyCode::NumMinus | KeyCode::NumPlus |
        KeyCode::NumEnter | KeyCode::NumPeriod | KeyCode::Num1 |
        KeyCode::Num2 | KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 |
        KeyCode::Num6 | KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 |
        KeyCode::Num0 => {
            if ctrl {
                Event::CtrlChar(blt_keycode_to_char(kc, shift))
            } else {
                Event::Char(blt_keycode_to_char(kc, shift))
            }
        }
    }
}

fn blt_keycode_to_char(kc: KeyCode, shift: bool) -> char {
    match kc {
        KeyCode::A => if shift { 'A' } else { 'a' },
        KeyCode::B => if shift { 'B' } else { 'b' },
        KeyCode::C => if shift { 'C' } else { 'c' },
        KeyCode::D => if shift { 'D' } else { 'd' },
        KeyCode::E => if shift { 'E' } else { 'e' },
        KeyCode::F => if shift { 'F' } else { 'f' },
        KeyCode::G => if shift { 'G' } else { 'g' },
        KeyCode::H => if shift { 'H' } else { 'h' },
        KeyCode::I => if shift { 'I' } else { 'i' },
        KeyCode::J => if shift { 'J' } else { 'j' },
        KeyCode::K => if shift { 'K' } else { 'k' },
        KeyCode::L => if shift { 'L' } else { 'l' },
        KeyCode::M => if shift { 'M' } else { 'm' },
        KeyCode::N => if shift { 'N' } else { 'n' },
        KeyCode::O => if shift { 'O' } else { 'o' },
        KeyCode::P => if shift { 'P' } else { 'p' },
        KeyCode::Q => if shift { 'Q' } else { 'q' },
        KeyCode::R => if shift { 'R' } else { 'r' },
        KeyCode::S => if shift { 'S' } else { 's' },
        KeyCode::T => if shift { 'T' } else { 't' },
        KeyCode::U => if shift { 'U' } else { 'u' },
        KeyCode::V => if shift { 'V' } else { 'v' },
        KeyCode::W => if shift { 'W' } else { 'w' },
        KeyCode::X => if shift { 'X' } else { 'x' },
        KeyCode::Y => if shift { 'Y' } else { 'y' },
        KeyCode::Z => if shift { 'Z' } else { 'z' },
        KeyCode::Row1 => if shift { '!' } else { '1' },
        KeyCode::Row2 => if shift { '@' } else { '2' },
        KeyCode::Row3 => if shift { '#' } else { '3' },
        KeyCode::Row4 => if shift { '$' } else { '4' },
        KeyCode::Row5 => if shift { '%' } else { '5' },
        KeyCode::Row6 => if shift { '^' } else { '6' },
        KeyCode::Row7 => if shift { '&' } else { '7' },
        KeyCode::Row8 => if shift { '*' } else { '8' },
        KeyCode::Row9 => if shift { '(' } else { '9' },
        KeyCode::Row0 => if shift { ')' } else { '0' },
        KeyCode::Grave => if shift { '~' } else { '`' },
        KeyCode::Minus => if shift { '_' } else { '-' },
        KeyCode::Equals => if shift { '+' } else { '=' },
        KeyCode::LeftBracket => if shift { '{' } else { '[' },
        KeyCode::RightBracket => if shift { '}' } else { ']' },
        KeyCode::Backslash => if shift { '|' } else { '\\' },
        KeyCode::Semicolon => if shift { ':' } else { ';' },
        KeyCode::Apostrophe => if shift { '"' } else { '\'' },
        KeyCode::Comma => if shift { '<' } else { ',' },
        KeyCode::Period => if shift { '>' } else { '.' },
        KeyCode::Slash => if shift { '?' } else { '/' },
        KeyCode::Space => ' ',
        KeyCode::NumDivide => '/',
        KeyCode::NumMultiply => '*',
        KeyCode::NumMinus => '-',
        KeyCode::NumPlus => '+',
        KeyCode::NumPeriod => '.',
        KeyCode::Num1 => '1',
        KeyCode::Num2 => '2',
        KeyCode::Num3 => '3',
        KeyCode::Num4 => '4',
        KeyCode::Num5 => '5',
        KeyCode::Num6 => '6',
        KeyCode::Num7 => '7',
        KeyCode::Num8 => '8',
        KeyCode::Num9 => '9',
        KeyCode::Num0 => '0',
        _ => unreachable!(),
    }
}

fn blt_keycode_to_key(kc: KeyCode) -> Key {
    match kc {
        KeyCode::F1 => Key::F1,
        KeyCode::F2 => Key::F2,
        KeyCode::F3 => Key::F3,
        KeyCode::F4 => Key::F4,
        KeyCode::F5 => Key::F5,
        KeyCode::F6 => Key::F6,
        KeyCode::F7 => Key::F7,
        KeyCode::F8 => Key::F8,
        KeyCode::F9 => Key::F9,
        KeyCode::F10 => Key::F10,
        KeyCode::F11 => Key::F11,
        KeyCode::F12 => Key::F12,
        KeyCode::Enter => Key::Enter,
        KeyCode::Escape => Key::Esc,
        KeyCode::Backspace => Key::Backspace,
        KeyCode::Tab => Key::Tab,
        KeyCode::Pause => Key::PauseBreak,
        KeyCode::Insert => Key::Ins,
        KeyCode::Home => Key::Home,
        KeyCode::PageUp => Key::PageUp,
        KeyCode::Delete => Key::Del,
        KeyCode::End => Key::End,
        KeyCode::PageDown => Key::PageDown,
        KeyCode::Right => Key::Right,
        KeyCode::Left => Key::Left,
        KeyCode::Down => Key::Down,
        KeyCode::Up => Key::Up,
        _ => unreachable!(),
    }
}
<commit_msg>Make window resizeable in BLT backend<commit_after>extern crate bear_lib_terminal;

use ::backend;
use ::event::{Event, Key};
use self::bear_lib_terminal::Color as BltColor;
use self::bear_lib_terminal::geometry::{Point, Size};
use self::bear_lib_terminal::terminal::{self, Event as BltEvent, KeyCode};
use std::collections::BTreeMap;
use std::io::Write;
use ::theme::{BaseColor, Color, ColorStyle, Effect};

pub struct Concrete {
    colours: BTreeMap<i16, (BltColor, BltColor)>,
}

impl backend::Backend for Concrete {
    fn init() -> Self {
        // TODO: Make this configurable?
        terminal::open("Cursive", 80, 24);
        terminal::set(terminal::config::Window::empty().resizeable(true));

        Concrete { colours: BTreeMap::new() }
    }

    fn finish(&mut self) {
        terminal::close();
    }

    fn init_color_style(&mut self, style: ColorStyle, foreground: &Color,
                        background: &Color) {
        self.colours.insert(style.id(),
                            (colour_to_blt_colour(foreground),
                             colour_to_blt_colour(background)));
    }

    fn with_color<F: FnOnce()>(&self, color: ColorStyle, f: F) {
        let (fg, bg) = self.colours[&color.id()];
        terminal::with_colors(fg, bg, f);
    }

    fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
        match effect {
            Effect::Simple => f(),
            // TODO: how to do this correctly?`
            //       BLT itself doesn't do this kind of thing,
            //       we'd need the colours in our position,
            //       but `f()` can do whatever
            Effect::Reverse => {
                terminal::with_colors(BltColor::from_rgb(0, 0, 0),
                                      BltColor::from_rgb(255, 255, 255),
                                      f)
            }
        }
    }

    fn has_colors(&self) -> bool {
        true
    }

    fn screen_size(&self) -> (usize, usize) {
        let Size { width, height } = terminal::state::size();
        (width as usize, height as usize)
    }

    fn clear(&self) {
        terminal::clear(None);
    }

    fn refresh(&mut self) {
        terminal::refresh();
    }

    fn print_at(&self, (x, y): (usize, usize), text: &str) {
        terminal::print_xy(x as i32, y as i32, text);
    }

    fn set_refresh_rate(&mut self, fps: u32) {
        // TODO: unsupported
    }

    fn poll_event(&self) -> Event {
        // TODO: we could add backend-specific controls here.
        // Ex: ctrl+mouse wheel cause window cellsize to change
        if let Some(ev) = terminal::wait_event() {
            match ev {
                BltEvent::Close => Event::Exit,
                BltEvent::Resize { .. } => Event::WindowResize,
                // TODO: mouse support
                BltEvent::MouseMove { .. } => Event::Refresh,
                BltEvent::MouseScroll { .. } => Event::Refresh,
                BltEvent::KeyPressed { key, ctrl, shift } => {
                    blt_keycode_to_ev(key, shift, ctrl)
                }
                // TODO: there's no Key::Shift/Ctrl for w/e reason
                BltEvent::ShiftPressed => Event::Refresh,
                BltEvent::ControlPressed => Event::Refresh,
                // TODO: what should we do here?
                BltEvent::KeyReleased { .. } |
                BltEvent::ShiftReleased |
                BltEvent::ControlReleased => Event::Refresh,
            }
        } else {
            Event::Refresh
        }
    }
}

fn colour_to_blt_colour(clr: &Color) -> BltColor {
    let (r, g, b) = match *clr {
        // Colours taken from
        // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
        Color::Dark(BaseColor::Black) => (0, 0, 0),
        Color::Dark(BaseColor::Red) => (170, 0, 0),
        Color::Dark(BaseColor::Green) => (0, 170, 0),
        Color::Dark(BaseColor::Yellow) => (170, 85, 0),
        Color::Dark(BaseColor::Blue) => (0, 0, 170),
        Color::Dark(BaseColor::Magenta) => (170, 0, 170),
        Color::Dark(BaseColor::Cyan) => (0, 170, 170),
        Color::Dark(BaseColor::White) => (170, 170, 170),

        Color::Light(BaseColor::Black) => (85, 85, 85),
        Color::Light(BaseColor::Red) => (255, 85, 85),
        Color::Light(BaseColor::Green) => (85, 255, 85),
        Color::Light(BaseColor::Yellow) => (255, 255, 85),
        Color::Light(BaseColor::Blue) => (85, 85, 255),
        Color::Light(BaseColor::Magenta) => (255, 85, 255),
        Color::Light(BaseColor::Cyan) => (85, 255, 255),
        Color::Light(BaseColor::White) => (255, 255, 255),

        Color::Rgb(r, g, b) => (r, g, b),
        Color::RgbLowRes(r, g, b) => {
            ((r as f32 / 5.0 * 255.0) as u8,
             (g as f32 / 5.0 * 255.0) as u8,
             (b as f32 / 5.0 * 255.0) as u8)
        }
    };
    BltColor::from_rgb(r, g, b)
}

fn blt_keycode_to_ev(kc: KeyCode, shift: bool, ctrl: bool) -> Event {
    match kc {
        KeyCode::F1 | KeyCode::F2 | KeyCode::F3 | KeyCode::F4 |
        KeyCode::F5 | KeyCode::F6 | KeyCode::F7 | KeyCode::F8 |
        KeyCode::F9 | KeyCode::F10 | KeyCode::F11 | KeyCode::F12 |
        KeyCode::Enter | KeyCode::Escape | KeyCode::Backspace |
        KeyCode::Tab | KeyCode::Pause | KeyCode::Insert | KeyCode::Home |
        KeyCode::PageUp | KeyCode::Delete | KeyCode::End |
        KeyCode::PageDown | KeyCode::Right | KeyCode::Left |
        KeyCode::Down | KeyCode::Up => {
            match (shift, ctrl) {
                (true, true) => Event::CtrlShift(blt_keycode_to_key(kc)),
                (true, false) => Event::Shift(blt_keycode_to_key(kc)),
                (false, true) => Event::Ctrl(blt_keycode_to_key(kc)),
                (false, false) => Event::Key(blt_keycode_to_key(kc)),
            }
        }
        // TODO: mouse support
        KeyCode::MouseLeft | KeyCode::MouseRight | KeyCode::MouseMiddle |
        KeyCode::MouseFourth | KeyCode::MouseFifth => Event::Refresh,
        KeyCode::A | KeyCode::B | KeyCode::C | KeyCode::D | KeyCode::E |
        KeyCode::F | KeyCode::G | KeyCode::H | KeyCode::I | KeyCode::J |
        KeyCode::K | KeyCode::L | KeyCode::M | KeyCode::N | KeyCode::O |
        KeyCode::P | KeyCode::Q | KeyCode::R | KeyCode::S | KeyCode::T |
        KeyCode::U | KeyCode::V | KeyCode::W | KeyCode::X | KeyCode::Y |
        KeyCode::Z | KeyCode::Row1 | KeyCode::Row2 | KeyCode::Row3 |
        KeyCode::Row4 | KeyCode::Row5 | KeyCode::Row6 | KeyCode::Row7 |
        KeyCode::Row8 | KeyCode::Row9 | KeyCode::Row0 | KeyCode::Grave |
        KeyCode::Minus | KeyCode::Equals | KeyCode::LeftBracket |
        KeyCode::RightBracket | KeyCode::Backslash | KeyCode::Semicolon |
        KeyCode::Apostrophe | KeyCode::Comma | KeyCode::Period |
        KeyCode::Slash | KeyCode::Space | KeyCode::NumDivide |
        KeyCode::NumMultiply | KeyCode::NumMinus | KeyCode::NumPlus |
        KeyCode::NumEnter | KeyCode::NumPeriod | KeyCode::Num1 |
        KeyCode::Num2 | KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 |
        KeyCode::Num6 | KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 |
        KeyCode::Num0 => {
            if ctrl {
                Event::CtrlChar(blt_keycode_to_char(kc, shift))
            } else {
                Event::Char(blt_keycode_to_char(kc, shift))
            }
        }
    }
}

fn blt_keycode_to_char(kc: KeyCode, shift: bool) -> char {
    match kc {
        KeyCode::A => if shift { 'A' } else { 'a' },
        KeyCode::B => if shift { 'B' } else { 'b' },
        KeyCode::C => if shift { 'C' } else { 'c' },
        KeyCode::D => if shift { 'D' } else { 'd' },
        KeyCode::E => if shift { 'E' } else { 'e' },
        KeyCode::F => if shift { 'F' } else { 'f' },
        KeyCode::G => if shift { 'G' } else { 'g' },
        KeyCode::H => if shift { 'H' } else { 'h' },
        KeyCode::I => if shift { 'I' } else { 'i' },
        KeyCode::J => if shift { 'J' } else { 'j' },
        KeyCode::K => if shift { 'K' } else { 'k' },
        KeyCode::L => if shift { 'L' } else { 'l' },
        KeyCode::M => if shift { 'M' } else { 'm' },
        KeyCode::N => if shift { 'N' } else { 'n' },
        KeyCode::O => if shift { 'O' } else { 'o' },
        KeyCode::P => if shift { 'P' } else { 'p' },
        KeyCode::Q => if shift { 'Q' } else { 'q' },
        KeyCode::R => if shift { 'R' } else { 'r' },
        KeyCode::S => if shift { 'S' } else { 's' },
        KeyCode::T => if shift { 'T' } else { 't' },
        KeyCode::U => if shift { 'U' } else { 'u' },
        KeyCode::V => if shift { 'V' } else { 'v' },
        KeyCode::W => if shift { 'W' } else { 'w' },
        KeyCode::X => if shift { 'X' } else { 'x' },
        KeyCode::Y => if shift { 'Y' } else { 'y' },
        KeyCode::Z => if shift { 'Z' } else { 'z' },
        KeyCode::Row1 => if shift { '!' } else { '1' },
        KeyCode::Row2 => if shift { '@' } else { '2' },
        KeyCode::Row3 => if shift { '#' } else { '3' },
        KeyCode::Row4 => if shift { '$' } else { '4' },
        KeyCode::Row5 => if shift { '%' } else { '5' },
        KeyCode::Row6 => if shift { '^' } else { '6' },
        KeyCode::Row7 => if shift { '&' } else { '7' },
        KeyCode::Row8 => if shift { '*' } else { '8' },
        KeyCode::Row9 => if shift { '(' } else { '9' },
        KeyCode::Row0 => if shift { ')' } else { '0' },
        KeyCode::Grave => if shift { '~' } else { '`' },
        KeyCode::Minus => if shift { '_' } else { '-' },
        KeyCode::Equals => if shift { '+' } else { '=' },
        KeyCode::LeftBracket => if shift { '{' } else { '[' },
        KeyCode::RightBracket => if shift { '}' } else { ']' },
        KeyCode::Backslash => if shift { '|' } else { '\\' },
        KeyCode::Semicolon => if shift { ':' } else { ';' },
        KeyCode::Apostrophe => if shift { '"' } else { '\'' },
        KeyCode::Comma => if shift { '<' } else { ',' },
        KeyCode::Period => if shift { '>' } else { '.' },
        KeyCode::Slash => if shift { '?' } else { '/' },
        KeyCode::Space => ' ',
        KeyCode::NumDivide => '/',
        KeyCode::NumMultiply => '*',
        KeyCode::NumMinus => '-',
        KeyCode::NumPlus => '+',
        KeyCode::NumPeriod => '.',
        KeyCode::Num1 => '1',
        KeyCode::Num2 => '2',
        KeyCode::Num3 => '3',
        KeyCode::Num4 => '4',
        KeyCode::Num5 => '5',
        KeyCode::Num6 => '6',
        KeyCode::Num7 => '7',
        KeyCode::Num8 => '8',
        KeyCode::Num9 => '9',
        KeyCode::Num0 => '0',
        _ => unreachable!(),
    }
}

fn blt_keycode_to_key(kc: KeyCode) -> Key {
    match kc {
        KeyCode::F1 => Key::F1,
        KeyCode::F2 => Key::F2,
        KeyCode::F3 => Key::F3,
        KeyCode::F4 => Key::F4,
        KeyCode::F5 => Key::F5,
        KeyCode::F6 => Key::F6,
        KeyCode::F7 => Key::F7,
        KeyCode::F8 => Key::F8,
        KeyCode::F9 => Key::F9,
        KeyCode::F10 => Key::F10,
        KeyCode::F11 => Key::F11,
        KeyCode::F12 => Key::F12,
        KeyCode::Enter => Key::Enter,
        KeyCode::Escape => Key::Esc,
        KeyCode::Backspace => Key::Backspace,
        KeyCode::Tab => Key::Tab,
        KeyCode::Pause => Key::PauseBreak,
        KeyCode::Insert => Key::Ins,
        KeyCode::Home => Key::Home,
        KeyCode::PageUp => Key::PageUp,
        KeyCode::Delete => Key::Del,
        KeyCode::End => Key::End,
        KeyCode::PageDown => Key::PageDown,
        KeyCode::Right => Key::Right,
        KeyCode::Left => Key::Left,
        KeyCode::Down => Key::Down,
        KeyCode::Up => Key::Up,
        _ => unreachable!(),
    }
}
<|endoftext|>