<commit_before>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::future;
use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

// A wrapper with fields the player needs
#[derive(Debug, Clone)]
pub struct AudioItem {
    pub id: SpotifyId,
    pub uri: String,
    pub files: LinearMap<FileFormat, FileId>,
    pub name: String,
    pub available: bool,
    pub alternatives: Option<Vec<SpotifyId>>,
}

impl AudioItem {
    pub fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        match id.audio_type {
            SpotifyAudioType::Track => Track::get_audio_item(session, id),
            SpotifyAudioType::Podcast => Episode::get_audio_item(session, id),
            SpotifyAudioType::NonPlayable => {
                Box::new(future::err::<AudioItem, MercuryError>(MercuryError))
            }
        }
    }
}

trait AudioFiles {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>>;
}

impl AudioFiles for Track {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        Box::new(Self::get(session, id).and_then(move |item| {
            Ok(AudioItem {
                id: id,
                uri: format!("spotify:track:{}", id.to_base62()),
                files: item.files,
                name: item.name,
                available: item.available,
                alternatives: Some(item.alternatives),
            })
        }))
    }
}

impl AudioFiles for Episode {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        Box::new(Self::get(session, id).and_then(move |item| {
            Ok(AudioItem {
                id: id,
                uri: format!("spotify:episode:{}", id.to_base62()),
                files: item.files,
                name: item.name,
                available: item.available,
                alternatives: None,
            })
        }))
    }
}
pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

<<<<<<< HEAD
    fn get(session: &Session, id: SpotifyId) -> Box<dyn Future<Item = Self, Error = MercuryError>> {
        let uri = format!("{}/{}", Self::base_url(), id.to_base16());
=======
    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
>>>>>>> Make standard metadata get handle also playlists
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

pub trait PlaylistMeta: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn base_url() -> &'static str;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

    fn get(session: &Session, id: SpotifyId, user: String, start: i32, len: i32) -> Box<Future<Item = Self, Error = MercuryError>> {
        //let uri = format!("hm://playlist/{}?from={}&length={}",id.to_base62(), 0, 100);
        //let uri = format!("hm://playlist/user/{}/playlist/{}?from={}&length={}", user, id.to_base62(), start, len);
        let uri = format!("hm://playlist/v2/playlist/{}", id.to_base62());
        println!("request uri: {}", uri);
        let request = session.mercury().get(uri);
        println!("a");
        let session = session.clone();
        Box::new(request.and_then(move |response| {
            println!("{:?}", response);
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();
            println!("{:?}", msg);
            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Episode {
    pub id: SpotifyId,
    pub name: String,
    pub external_url: String,
    pub duration: i32,
    pub language: String,
    pub show: SpotifyId,
    pub files: LinearMap<FileFormat, FileId>,
    pub covers: Vec<FileId>,
    pub available: bool,
    pub explicit: bool,
}

#[derive(Debug, Clone)]
pub struct Show {
    pub id: SpotifyId,
    pub name: String,
    pub publisher: String,
    pub episodes: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn base_url() -> &'static str {
        "hm://playlist/v2/playlist"
    }

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = format!("{}/{}", Self::base_url(), id.to_base62());
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        println!("parse Message: {:?}", msg);
        Playlist {
            //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

// Podcast
impl Metadata for Episode {
    type Message = protocol::metadata::Episode;

    fn base_url() -> &'static str {
        "hm://metadata/3/episode"
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        let covers = msg
            .get_covers()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Episode {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            external_url: msg.get_external_url().to_owned(),
            duration: msg.get_duration().to_owned(),
            language: msg.get_language().to_owned(),
            show: SpotifyId::from_raw(msg.get_show().get_gid()).unwrap(),
            covers: covers,
            files: files,
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
            explicit: msg.get_explicit().to_owned(),
        }
    }
}

impl Metadata for Show {
    type Message = protocol::metadata::Show;

    fn base_url() -> &'static str {
        "hm://metadata/3/show"
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let episodes = msg
            .get_episode()
            .iter()
            .filter(|episode| episode.has_gid())
            .map(|episode| SpotifyId::from_raw(episode.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_covers()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Show {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            publisher: msg.get_publisher().to_owned(),
            episodes: episodes,
            covers: covers,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<commit_msg>Remove unused code from metadata lib<commit_after>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::future;
use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

// A wrapper with fields the player needs
#[derive(Debug, Clone)]
pub struct AudioItem {
    pub id: SpotifyId,
    pub uri: String,
    pub files: LinearMap<FileFormat, FileId>,
    pub name: String,
    pub available: bool,
    pub alternatives: Option<Vec<SpotifyId>>,
}

impl AudioItem {
    pub fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        match id.audio_type {
            SpotifyAudioType::Track => Track::get_audio_item(session, id),
            SpotifyAudioType::Podcast => Episode::get_audio_item(session, id),
            SpotifyAudioType::NonPlayable => {
                Box::new(future::err::<AudioItem, MercuryError>(MercuryError))
            }
        }
    }
}

trait AudioFiles {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>>;
}

impl AudioFiles for Track {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        Box::new(Self::get(session, id).and_then(move |item| {
            Ok(AudioItem {
                id: id,
                uri: format!("spotify:track:{}", id.to_base62()),
                files: item.files,
                name: item.name,
                available: item.available,
                alternatives: Some(item.alternatives),
            })
        }))
    }
}

impl AudioFiles for Episode {
    fn get_audio_item(
        session: &Session,
        id: SpotifyId,
    ) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
        Box::new(Self::get(session, id).and_then(move |item| {
            Ok(AudioItem {
                id: id,
                uri: format!("spotify:episode:{}", id.to_base62()),
                files: item.files,
                name: item.name,
                available: item.available,
                alternatives: None,
            })
        }))
    }
}
pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

<<<<<<< HEAD
    fn get(session: &Session, id: SpotifyId) -> Box<dyn Future<Item = Self, Error = MercuryError>> {
        let uri = format!("{}/{}", Self::base_url(), id.to_base16());
=======
    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
>>>>>>> Make standard metadata get handle also playlists
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Episode {
    pub id: SpotifyId,
    pub name: String,
    pub external_url: String,
    pub duration: i32,
    pub language: String,
    pub show: SpotifyId,
    pub files: LinearMap<FileFormat, FileId>,
    pub covers: Vec<FileId>,
    pub available: bool,
    pub explicit: bool,
}

#[derive(Debug, Clone)]
pub struct Show {
    pub id: SpotifyId,
    pub name: String,
    pub publisher: String,
    pub episodes: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn base_url() -> &'static str {
        "hm://playlist/v2/playlist"
    }

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = format!("{}/{}", Self::base_url(), id.to_base62());
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        println!("parse Message: {:?}", msg);
        Playlist {
            //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

// Podcast
impl Metadata for Episode {
    type Message = protocol::metadata::Episode;

    fn base_url() -> &'static str {
        "hm://metadata/3/episode"
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        let covers = msg
            .get_covers()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Episode {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            external_url: msg.get_external_url().to_owned(),
            duration: msg.get_duration().to_owned(),
            language: msg.get_language().to_owned(),
            show: SpotifyId::from_raw(msg.get_show().get_gid()).unwrap(),
            covers: covers,
            files: files,
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
            explicit: msg.get_explicit().to_owned(),
        }
    }
}

impl Metadata for Show {
    type Message = protocol::metadata::Show;

    fn base_url() -> &'static str {
        "hm://metadata/3/show"
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let episodes = msg
            .get_episode()
            .iter()
            .filter(|episode| episode.has_gid())
            .map(|episode| SpotifyId::from_raw(episode.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_covers()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Show {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            publisher: msg.get_publisher().to_owned(),
            episodes: episodes,
            covers: covers,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<|endoftext|>


<commit_before>use std::cell::RefCell;

use proc_macro2::{Delimiter, Span};

use buffer::Cursor;
use error::{self, Error};
use span::IntoSpans;
use token::Token;

/// Support for checking the next token in a stream to decide how to parse.
///
/// An important advantage over [`ParseStream::peek`] is that here we
/// automatically construct an appropriate error message based on the token
/// alternatives that get peeked. If you are producing your own error message,
/// go ahead and use `ParseStream::peek` instead.
///
/// Use [`ParseStream::lookahead1`] to construct this object.
///
/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
/// [`ParseStream::lookahead1`]: struct.ParseBuffer.html#method.lookahead1
///
/// # Example
///
/// ```edition2018
/// use syn::{ConstParam, Ident, Lifetime, LifetimeDef, Result, Token, TypeParam};
/// use syn::parse::{Parse, ParseStream};
///
/// // A generic parameter, a single one of the comma-separated elements inside
/// // angle brackets in:
/// //
/// //     fn f<T: Clone, 'a, 'b: 'a, const N: usize>() { ... }
/// //
/// // On invalid input, lookahead gives us a reasonable error message.
/// //
/// //     error: expected one of: identifier, lifetime, `const`
/// //       |
/// //     5 |     fn f<!Sized>() {}
/// //       |          ^
/// enum GenericParam {
///     Type(TypeParam),
///     Lifetime(LifetimeDef),
///     Const(ConstParam),
/// }
///
/// impl Parse for GenericParam {
///     fn parse(input: ParseStream) -> Result<Self> {
///         let lookahead = input.lookahead1();
///         if lookahead.peek(Ident) {
///             input.parse().map(GenericParam::Type)
///         } else if lookahead.peek(Lifetime) {
///             input.parse().map(GenericParam::Lifetime)
///         } else if lookahead.peek(Token![const]) {
///             input.parse().map(GenericParam::Const)
///         } else {
///             Err(lookahead.error())
///         }
///     }
/// }
/// ```
pub struct Lookahead1<'a> {
    scope: Span,
    cursor: Cursor<'a>,
    comparisons: RefCell<Vec<&'static str>>,
}

pub fn new(scope: Span, cursor: Cursor) -> Lookahead1 {
    Lookahead1 {
        scope: scope,
        cursor: cursor,
        comparisons: RefCell::new(Vec::new()),
    }
}

fn peek_impl(
    lookahead: &Lookahead1,
    peek: fn(Cursor) -> bool,
    display: fn() -> &'static str,
) -> bool {
    if peek(lookahead.cursor) {
        return true;
    }
    lookahead.comparisons.borrow_mut().push(display());
    false
}

impl<'a> Lookahead1<'a> {
    /// Looks at the next token in the parse stream to determine whether it
    /// matches the requested type of token.
    ///
    /// # Syntax
    ///
    /// Note that this method does not use turbofish syntax. Pass the peek type
    /// inside of parentheses.
    ///
    /// - `input.peek(Token![struct])`
    /// - `input.peek(Token![==])`
    /// - `input.peek(Ident)`
    /// - `input.peek(Lifetime)`
    /// - `input.peek(token::Brace)`
    pub fn peek<T: Peek>(&self, token: T) -> bool {
        let _ = token;
        peek_impl(self, T::Token::peek, T::Token::display)
    }

    /// Triggers an error at the current position of the parse stream.
    ///
    /// The error message will identify all of the expected token types that
    /// have been peeked against this lookahead instance.
    pub fn error(self) -> Error {
        let comparisons = self.comparisons.borrow();
        match comparisons.len() {
            0 => {
                if self.cursor.eof() {
                    Error::new(self.scope, "unexpected end of input")
                } else {
                    Error::new(self.cursor.span(), "unexpected token")
                }
            }
            1 => {
                let message = format!("expected {}", comparisons[0]);
                error::new_at(self.scope, self.cursor, message)
            }
            2 => {
                let message = format!("expected {} or {}", comparisons[0], comparisons[1]);
                error::new_at(self.scope, self.cursor, message)
            }
            _ => {
                let join = comparisons.join(", ");
                let message = format!("expected one of: {}", join);
                error::new_at(self.scope, self.cursor, message)
            }
        }
    }
}

/// Types that can be parsed by looking at just one token.
///
/// Use [`ParseStream::peek`] to peek one of these types in a parse stream
/// without consuming it from the stream.
///
/// This trait is sealed and cannot be implemented for types outside of Syn.
///
/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
pub trait Peek: private::Sealed {
    // Not public API.
    #[doc(hidden)]
    type Token: Token;
}

impl<F: FnOnce(TokenMarker) -> T, T: Token> Peek for F {
    type Token = T;
}

pub enum TokenMarker {}

impl<S> IntoSpans<S> for TokenMarker {
    fn into_spans(self) -> S {
        match self {}
    }
}

pub fn is_delimiter(cursor: Cursor, delimiter: Delimiter) -> bool {
    cursor.group(delimiter).is_some()
}

mod private {
    use super::{Token, TokenMarker};
    pub trait Sealed {}
    impl<F: FnOnce(TokenMarker) -> T, T: Token> Sealed for F {}
}
<commit_msg>Guarantee that the peek indicator is Copy<commit_after>use std::cell::RefCell;

use proc_macro2::{Delimiter, Span};

use buffer::Cursor;
use error::{self, Error};
use span::IntoSpans;
use token::Token;

/// Support for checking the next token in a stream to decide how to parse.
///
/// An important advantage over [`ParseStream::peek`] is that here we
/// automatically construct an appropriate error message based on the token
/// alternatives that get peeked. If you are producing your own error message,
/// go ahead and use `ParseStream::peek` instead.
///
/// Use [`ParseStream::lookahead1`] to construct this object.
///
/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
/// [`ParseStream::lookahead1`]: struct.ParseBuffer.html#method.lookahead1
///
/// # Example
///
/// ```edition2018
/// use syn::{ConstParam, Ident, Lifetime, LifetimeDef, Result, Token, TypeParam};
/// use syn::parse::{Parse, ParseStream};
///
/// // A generic parameter, a single one of the comma-separated elements inside
/// // angle brackets in:
/// //
/// //     fn f<T: Clone, 'a, 'b: 'a, const N: usize>() { ... }
/// //
/// // On invalid input, lookahead gives us a reasonable error message.
/// //
/// //     error: expected one of: identifier, lifetime, `const`
/// //       |
/// //     5 |     fn f<!Sized>() {}
/// //       |          ^
/// enum GenericParam {
///     Type(TypeParam),
///     Lifetime(LifetimeDef),
///     Const(ConstParam),
/// }
///
/// impl Parse for GenericParam {
///     fn parse(input: ParseStream) -> Result<Self> {
///         let lookahead = input.lookahead1();
///         if lookahead.peek(Ident) {
///             input.parse().map(GenericParam::Type)
///         } else if lookahead.peek(Lifetime) {
///             input.parse().map(GenericParam::Lifetime)
///         } else if lookahead.peek(Token![const]) {
///             input.parse().map(GenericParam::Const)
///         } else {
///             Err(lookahead.error())
///         }
///     }
/// }
/// ```
pub struct Lookahead1<'a> {
    scope: Span,
    cursor: Cursor<'a>,
    comparisons: RefCell<Vec<&'static str>>,
}

pub fn new(scope: Span, cursor: Cursor) -> Lookahead1 {
    Lookahead1 {
        scope: scope,
        cursor: cursor,
        comparisons: RefCell::new(Vec::new()),
    }
}

fn peek_impl(
    lookahead: &Lookahead1,
    peek: fn(Cursor) -> bool,
    display: fn() -> &'static str,
) -> bool {
    if peek(lookahead.cursor) {
        return true;
    }
    lookahead.comparisons.borrow_mut().push(display());
    false
}

impl<'a> Lookahead1<'a> {
    /// Looks at the next token in the parse stream to determine whether it
    /// matches the requested type of token.
    ///
    /// # Syntax
    ///
    /// Note that this method does not use turbofish syntax. Pass the peek type
    /// inside of parentheses.
    ///
    /// - `input.peek(Token![struct])`
    /// - `input.peek(Token![==])`
    /// - `input.peek(Ident)`
    /// - `input.peek(Lifetime)`
    /// - `input.peek(token::Brace)`
    pub fn peek<T: Peek>(&self, token: T) -> bool {
        let _ = token;
        peek_impl(self, T::Token::peek, T::Token::display)
    }

    /// Triggers an error at the current position of the parse stream.
    ///
    /// The error message will identify all of the expected token types that
    /// have been peeked against this lookahead instance.
    pub fn error(self) -> Error {
        let comparisons = self.comparisons.borrow();
        match comparisons.len() {
            0 => {
                if self.cursor.eof() {
                    Error::new(self.scope, "unexpected end of input")
                } else {
                    Error::new(self.cursor.span(), "unexpected token")
                }
            }
            1 => {
                let message = format!("expected {}", comparisons[0]);
                error::new_at(self.scope, self.cursor, message)
            }
            2 => {
                let message = format!("expected {} or {}", comparisons[0], comparisons[1]);
                error::new_at(self.scope, self.cursor, message)
            }
            _ => {
                let join = comparisons.join(", ");
                let message = format!("expected one of: {}", join);
                error::new_at(self.scope, self.cursor, message)
            }
        }
    }
}

/// Types that can be parsed by looking at just one token.
///
/// Use [`ParseStream::peek`] to peek one of these types in a parse stream
/// without consuming it from the stream.
///
/// This trait is sealed and cannot be implemented for types outside of Syn.
///
/// [`ParseStream::peek`]: struct.ParseBuffer.html#method.peek
pub trait Peek: private::Sealed {
    // Not public API.
    #[doc(hidden)]
    type Token: Token;
}

impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Peek for F {
    type Token = T;
}

pub enum TokenMarker {}

impl<S> IntoSpans<S> for TokenMarker {
    fn into_spans(self) -> S {
        match self {}
    }
}

pub fn is_delimiter(cursor: Cursor, delimiter: Delimiter) -> bool {
    cursor.group(delimiter).is_some()
}

mod private {
    use super::{Token, TokenMarker};
    pub trait Sealed: Copy {}
    impl<F: Copy + FnOnce(TokenMarker) -> T, T: Token> Sealed for F {}
}
<|endoftext|>


<commit_before>//! Locale implementation using GNU libc

use ::std::sync::Arc;
use ::std::io::{Error,Result};
use super::{LocaleFactory,Numeric,Time};

pub mod ffi;
pub mod langinfo;

/// Wrapper for libc's locale_t.
#[derive(Debug)]
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> {
        let cloc = try!(::std::ffi::CString::new(locale));
        let res = unsafe { ffi::newlocale(ffi::LC_ALL_MASK, cloc.as_ptr(), ::std::ptr::null_mut()) };
        if res.is_null() {
            Err(Error::last_os_error())
        } 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> {
        let cloc = try!(::std::ffi::CString::new(locale));
        let res = unsafe { ffi::newlocale(mask, cloc.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(Error::last_os_error())
        } else {
            Ok(CLocale { c_locale: 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) },
        }
    }
}

#[derive(Debug)]
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> {
        let cto = try!(::std::ffi::CString::new(to));
        let cfrom = try!(::std::ffi::CString::new(from));
        let res = unsafe { ffi::iconv_open(cto.as_ptr(), cfrom.as_ptr()) };
        if res.is_null() {
            Err(Error::last_os_error())
        } 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 `errno` (unfortunately
    ///     `::std::io::Error::last_os_error()` does not seem to be able to distinguish them at the
    ///     moment).
    ///  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); }
        }
    }
}

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

impl LibCLocaleFactory {
    fn codeset_index(item: langinfo::CodesetItems) -> usize {
        match item {
            langinfo::_NL_COLLATE_CODESET => 0,
            langinfo::_NL_CTYPE_CODESET_NAME => 1,
            langinfo::_NL_MONETARY_CODESET => 2,
            langinfo::_NL_NUMERIC_CODESET => 3,
            langinfo::_NL_TIME_CODESET => 4,
            langinfo::_NL_MESSAGES_CODESET => 5,
            langinfo::_NL_PAPER_CODESET => 6,
            langinfo::_NL_NAME_CODESET => 7,
            langinfo::_NL_ADDRESS_CODESET => 8,
            langinfo::_NL_TELEPHONE_CODESET => 9,
            langinfo::_NL_MEASUREMENT_CODESET => 10,
            langinfo::_NL_IDENTIFICATION_CODESET => 11,
        }
    }

    // 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_from_c_locale(c_locale: CLocale) -> Self {
        fn get_iconv(codeset: langinfo::CodesetItems, locale: &CLocale) -> Option<Arc<IConv>> {
            let cs = unsafe {
                ::std::str::from_utf8_unchecked(
                    ::std::ffi::CStr::from_ptr(
                        ffi::nl_langinfo_l(codeset as ::libc::c_uint, locale.c_locale)).to_bytes())
            };
            if cs != "UTF-8" {
                if let Ok(i) = IConv::new("UTF-8", cs) {
                    return Some(Arc::new(i));
                }
            }
            return None;
        }
        return LibCLocaleFactory{
            iconv: [
                get_iconv(langinfo::_NL_COLLATE_CODESET, &c_locale),
                get_iconv(langinfo::_NL_CTYPE_CODESET_NAME, &c_locale),
                get_iconv(langinfo::_NL_MONETARY_CODESET, &c_locale),
                get_iconv(langinfo::_NL_NUMERIC_CODESET, &c_locale),
                get_iconv(langinfo::_NL_TIME_CODESET, &c_locale),
                get_iconv(langinfo::_NL_MESSAGES_CODESET, &c_locale),
                get_iconv(langinfo::_NL_PAPER_CODESET, &c_locale),
                get_iconv(langinfo::_NL_NAME_CODESET, &c_locale),
                get_iconv(langinfo::_NL_ADDRESS_CODESET, &c_locale),
                get_iconv(langinfo::_NL_TELEPHONE_CODESET, &c_locale),
                get_iconv(langinfo::_NL_MEASUREMENT_CODESET, &c_locale),
                get_iconv(langinfo::_NL_IDENTIFICATION_CODESET, &c_locale),
            ],
            locale: Arc::new(c_locale),
        };
    }

    pub fn new(locale: &str) -> Result<Self> {
        let loc = try!(CLocale::new(locale));

        return Ok(LibCLocaleFactory::new_from_c_locale(loc));
    }

    pub fn langinfo<'a, I>(&'a self, item: I) -> I::Type
        where I: langinfo::LanginfoItem<'a>
    {
        let mut conv = None;
        if let Some(cs) = I::needs_iconv() {
            if let Some(ref iconv) = self.iconv[LibCLocaleFactory::codeset_index(cs)] {
                conv = Some(&**iconv);
            }
        }
        unsafe {
            item.decode(ffi::nl_langinfo_l(item.to_ffi(), self.locale.c_locale), conv)
        }
    }
}

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

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

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

    fn has_locale(locale: &str) -> bool {
        CLocale::new(locale).is_ok()
    }

    #[test]
    fn c_locale() {
        if has_locale("C.UTF-8") {
            let l = LibCLocaleFactory::new("C.UTF-8").unwrap();
            assert_eq!("UTF-8", l.langinfo(langinfo::CODESET));
        } else {
            println!("Skipped!");
        }
    }

    #[test]
    fn en_locale() {
        if has_locale("en_GB") {
            let l = LibCLocaleFactory::new("en_GB").unwrap();
            assert_eq!("ISO-8859-1", l.langinfo(langinfo::CODESET));
        } else {
            println!("Skipped!");
        }
    }

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

    #[test]
    fn mixed_locale() {
        fn langinfo(loc: &CLocale, item: ::libc::c_uint) -> &str {
            let res = unsafe { CStr::from_ptr(ffi::nl_langinfo_l(item, loc.c_locale)) };
            ::std::str::from_utf8(res.to_bytes()).unwrap()
        }

        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(langinfo::CODESET));
            assert_eq!("Út", lf.langinfo(langinfo::ABDAY_3));
        }
    }
}
<commit_msg>Getting per-category locale identifier for Linux.<commit_after>//! Locale implementation using GNU libc

use ::std::borrow::Cow;
use ::std::ffi::{CStr,CString};
use ::std::io::{Error,Result};
use ::std::sync::Arc;
use super::{LocaleFactory,Numeric,Time};

pub mod ffi;
pub mod langinfo;

/// Wrapper for libc's locale_t.
#[derive(Debug)]
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> {
        let cloc = try!(CString::new(locale));
        let res = unsafe { ffi::newlocale(ffi::LC_ALL_MASK, cloc.as_ptr(), ::std::ptr::null_mut()) };
        if res.is_null() {
            Err(Error::last_os_error())
        } 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> {
        let cloc = try!(CString::new(locale));
        let res = unsafe { ffi::newlocale(mask, cloc.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(Error::last_os_error())
        } else {
            Ok(CLocale { c_locale: res, })
        }
    }

    /// Returns locale ID that is in use for given category.
    ///
    /// As indicated by `locale_t::names[category]`.
    pub fn name<'a>(&'a self, category: ::libc::c_int) -> Cow<'a, str> {
        assert!(category >= 0 && category <= 12);
        unsafe {
            let ptr = (*self.c_locale).__names[category as usize];
            if ptr.is_null() {
                return Cow::Borrowed("C");
            }
            let cres: &'a CStr = CStr::from_ptr(ptr);
            return String::from_utf8_lossy(cres.to_bytes());
        }
    }
}

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) },
        }
    }
}

#[derive(Debug)]
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> {
        let cto = try!(::std::ffi::CString::new(to));
        let cfrom = try!(::std::ffi::CString::new(from));
        let res = unsafe { ffi::iconv_open(cto.as_ptr(), cfrom.as_ptr()) };
        if res.is_null() {
            Err(Error::last_os_error())
        } 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 `errno` (unfortunately
    ///     `::std::io::Error::last_os_error()` does not seem to be able to distinguish them at the
    ///     moment).
    ///  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); }
        }
    }
}

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

impl LibCLocaleFactory {
    fn codeset_index(item: langinfo::CodesetItems) -> usize {
        match item {
            langinfo::_NL_COLLATE_CODESET => 0,
            langinfo::_NL_CTYPE_CODESET_NAME => 1,
            langinfo::_NL_MONETARY_CODESET => 2,
            langinfo::_NL_NUMERIC_CODESET => 3,
            langinfo::_NL_TIME_CODESET => 4,
            langinfo::_NL_MESSAGES_CODESET => 5,
            langinfo::_NL_PAPER_CODESET => 6,
            langinfo::_NL_NAME_CODESET => 7,
            langinfo::_NL_ADDRESS_CODESET => 8,
            langinfo::_NL_TELEPHONE_CODESET => 9,
            langinfo::_NL_MEASUREMENT_CODESET => 10,
            langinfo::_NL_IDENTIFICATION_CODESET => 11,
        }
    }

    // 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_from_c_locale(c_locale: CLocale) -> Self {
        fn get_iconv(codeset: langinfo::CodesetItems, locale: &CLocale) -> Option<Arc<IConv>> {
            let cs = unsafe {
                ::std::str::from_utf8_unchecked(
                    ::std::ffi::CStr::from_ptr(
                        ffi::nl_langinfo_l(codeset as ::libc::c_uint, locale.c_locale)).to_bytes())
            };
            if cs != "UTF-8" {
                if let Ok(i) = IConv::new("UTF-8", cs) {
                    return Some(Arc::new(i));
                }
            }
            return None;
        }
        return LibCLocaleFactory{
            iconv: [
                get_iconv(langinfo::_NL_COLLATE_CODESET, &c_locale),
                get_iconv(langinfo::_NL_CTYPE_CODESET_NAME, &c_locale),
                get_iconv(langinfo::_NL_MONETARY_CODESET, &c_locale),
                get_iconv(langinfo::_NL_NUMERIC_CODESET, &c_locale),
                get_iconv(langinfo::_NL_TIME_CODESET, &c_locale),
                get_iconv(langinfo::_NL_MESSAGES_CODESET, &c_locale),
                get_iconv(langinfo::_NL_PAPER_CODESET, &c_locale),
                get_iconv(langinfo::_NL_NAME_CODESET, &c_locale),
                get_iconv(langinfo::_NL_ADDRESS_CODESET, &c_locale),
                get_iconv(langinfo::_NL_TELEPHONE_CODESET, &c_locale),
                get_iconv(langinfo::_NL_MEASUREMENT_CODESET, &c_locale),
                get_iconv(langinfo::_NL_IDENTIFICATION_CODESET, &c_locale),
            ],
            locale: Arc::new(c_locale),
        };
    }

    pub fn new(locale: &str) -> Result<Self> {
        let loc = try!(CLocale::new(locale));

        return Ok(LibCLocaleFactory::new_from_c_locale(loc));
    }

    pub fn langinfo<'a, I>(&'a self, item: I) -> I::Type
        where I: langinfo::LanginfoItem<'a>
    {
        let mut conv = None;
        if let Some(cs) = I::needs_iconv() {
            if let Some(ref iconv) = self.iconv[LibCLocaleFactory::codeset_index(cs)] {
                conv = Some(&**iconv);
            }
        }
        unsafe {
            item.decode(ffi::nl_langinfo_l(item.to_ffi(), self.locale.c_locale), conv)
        }
    }
}

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

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

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

    fn has_locale(locale: &str) -> bool {
        CLocale::new(locale).is_ok()
    }

    #[test]
    fn c_locale() {
        if has_locale("C.UTF-8") {
            let l = LibCLocaleFactory::new("C.UTF-8").unwrap();
            assert_eq!("UTF-8", l.langinfo(langinfo::CODESET));
        } else {
            println!("Skipped!");
        }
    }

    #[test]
    fn en_locale() {
        if has_locale("en_GB") {
            let l = LibCLocaleFactory::new("en_GB").unwrap();
            assert_eq!("ISO-8859-1", l.langinfo(langinfo::CODESET));
        } else {
            println!("Skipped!");
        }
    }

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

    #[test]
    fn mixed_locale() {
        fn langinfo(loc: &CLocale, item: ::libc::c_uint) -> &str {
            let res = unsafe { CStr::from_ptr(ffi::nl_langinfo_l(item, loc.c_locale)) };
            ::std::str::from_utf8(res.to_bytes()).unwrap()
        }

        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));
                    assert_eq!("cs_CZ", n.name(ffi::LC_CTYPE));
                    assert_eq!("en_GB", n.name(ffi::LC_NUMERIC));
                    assert_eq!("de_DE", n.name(ffi::LC_TIME));
                }
            }
        }
    }

    #[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(langinfo::CODESET));
            assert_eq!("Út", lf.langinfo(langinfo::ABDAY_3));
        }
    }
}
<|endoftext|>


<commit_before>//! A module to handle `Event` enumerator

pub mod attributes;

use std::borrow::Cow;
use std::str::from_utf8;
use std::ops::Deref;
use encoding_rs::Encoding;
use std::io::BufRead;

use escape::{escape, unescape};
use self::attributes::{Attribute, Attributes};
use errors::{Error, Result};
use reader::Reader;

use memchr;

/// A struct to manage `Event::Start` events
///
/// Provides in particular an iterator over attributes
#[derive(Clone, Debug)]
pub struct BytesStart<'a> {
    /// content of the element, before any utf8 conversion
    buf: Cow<'a, [u8]>,
    /// end of the element name, the name starts at that the start of `buf`
    name_len: usize,
}

impl<'a> BytesStart<'a> {
    /// Creates a new `BytesStart` from the given name.
    #[inline]
    pub fn borrowed(content: &'a [u8], name_len: usize) -> BytesStart<'a> {
        BytesStart {
            buf: Cow::Borrowed(content),
            name_len: name_len,
        }
    }

    /// Creates a new `BytesStart` from the given name. Owns its content
    #[inline]
    pub fn owned(content: Vec<u8>, name_len: usize) -> BytesStart<'static> {
        BytesStart {
            buf: Cow::Owned(content),
            name_len: name_len,
        }
    }

    /// Converts the event into an Owned event
    pub fn into_owned(self) -> BytesStart<'static> {
        BytesStart {
            buf: Cow::Owned(self.buf.into_owned()),
            name_len: self.name_len,
        }
    }

    /// Consumes self and adds attributes to this element from an iterator
    /// over (key, value) tuples.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn with_attributes<'b, I>(mut self, attributes: I) -> Self
    where
        I: IntoIterator,
        I::Item: Into<Attribute<'b>>,
    {
        self.extend_attributes(attributes);
        self
    }

    /// name as &[u8] (without eventual attributes)
    pub fn name(&self) -> &[u8] {
        &self.buf[..self.name_len]
    }

    /// local name (excluding namespace) as &[u8] (without eventual attributes)
    /// returns the name() with any leading namespace removed (all content up to
    /// and including the first ':' character)
    #[inline]
    pub fn local_name(&self) -> &[u8] {
        let name = self.name();
        memchr::memchr(b':', name).map_or(name, |i| &name[i + 1..])
    }

    /// gets unescaped content
    ///
    /// Searches for '&' into content and try to escape the coded character if possible
    /// returns Malformed error with index within element if '&' is not followed by ';'
    pub fn unescaped(&self) -> Result<Cow<[u8]>> {
        unescape(&*self.buf).map_err(Error::EscapeError)
    }

    /// gets attributes iterator
    pub fn attributes(&self) -> Attributes {
        Attributes::new(self, self.name_len)
    }

    /// extend the attributes of this element from an iterator over (key, value) tuples.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn extend_attributes<'b, I>(&mut self, attributes: I) -> &mut BytesStart<'a>
    where
        I: IntoIterator,
        I::Item: Into<Attribute<'b>>,
    {
        for attr in attributes {
            self.push_attribute(attr);
        }
        self
    }

    /// helper method to unescape then decode self using the reader encoding
    ///
    /// for performance reasons (could avoid allocating a `String`),
    /// it might be wiser to manually use
    /// 1. BytesStart::unescaped()
    /// 2. Reader::decode(...)
    pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
        self.unescaped().map(|e| reader.decode(&*e).into_owned())
    }

    /// Adds an attribute to this element from the given key and value.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn push_attribute<'b, A: Into<Attribute<'b>>>(&mut self, attr: A) {
        let a = attr.into();
        let bytes = self.buf.to_mut();
        bytes.push(b' ');
        bytes.extend_from_slice(a.key);
        bytes.extend_from_slice(b"=\"");
        bytes.extend_from_slice(&*a.value);
        bytes.push(b'"');
    }
}

/// Wrapper around `BytesElement` to parse/write `XmlDecl`
///
/// Postpone element parsing only when needed.
///
/// [W3C XML 1.1 Prolog and Document Type Delcaration](http://w3.org/TR/xml11/#sec-prolog-dtd)
#[derive(Clone, Debug)]
pub struct BytesDecl<'a> {
    element: BytesStart<'a>,
}

impl<'a> BytesDecl<'a> {
    /// Creates a `BytesDecl` from a `BytesStart`
    pub fn from_start(start: BytesStart<'a>) -> BytesDecl<'a> {
        BytesDecl { element: start }
    }

    /// Gets xml version, including quotes (' or ")
    pub fn version(&self) -> Result<Cow<[u8]>> {
        match self.element.attributes().next() {
            Some(Err(e)) => Err(e),
            Some(Ok(Attribute {
                key: b"version",
                value: v,
            })) => Ok(v),
            Some(Ok(a)) => {
                let found = from_utf8(a.key).map_err(Error::Utf8)?.to_string();
                Err(Error::XmlDeclWithoutVersion(Some(found)))
            },
            None => Err(Error::XmlDeclWithoutVersion(None)),
        }
    }

    /// Gets xml encoding, including quotes (' or ")
    pub fn encoding(&self) -> Option<Result<Cow<[u8]>>> {
        for a in self.element.attributes() {
            match a {
                Err(e) => return Some(Err(e)),
                Ok(Attribute {
                    key: b"encoding",
                    value: v,
                }) => return Some(Ok(v)),
                _ => (),
            }
        }
        None
    }

    /// Gets xml standalone, including quotes (' or ")
    pub fn standalone(&self) -> Option<Result<Cow<[u8]>>> {
        for a in self.element.attributes() {
            match a {
                Err(e) => return Some(Err(e)),
                Ok(Attribute {
                    key: b"standalone",
                    value: v,
                }) => return Some(Ok(v)),
                _ => (),
            }
        }
        None
    }

    /// Constructs a new `XmlDecl` from the (mandatory) _version_ (should be `1.0` or `1.1`),
    /// the optional _encoding_ (e.g., `UTF-8`) and the optional _standalone_ (`yes` or `no`)
    /// attribute.
    ///
    /// Does not escape any of its inputs. Always uses double quotes to wrap the attribute values.
    /// The caller is responsible for escaping attribute values. Shouldn't usually be relevant since
    /// the double quote character is not allowed in any of the attribute values.
    pub fn new(
        version: &[u8],
        encoding: Option<&[u8]>,
        standalone: Option<&[u8]>,
    ) -> BytesDecl<'static> {
        // Compute length of the buffer based on supplied attributes
        // ' encoding=""'   => 12
        let encoding_attr_len = if let Some(xs) = encoding {
            12 + xs.len()
        } else {
            0
        };
        // ' standalone=""' => 14
        let standalone_attr_len = if let Some(xs) = standalone {
            14 + xs.len()
        } else {
            0
        };
        // 'xml version=""' => 14
        let mut buf = Vec::with_capacity(14 + encoding_attr_len + standalone_attr_len);

        buf.extend_from_slice(b"xml version=\"");
        buf.extend_from_slice(version);

        if let Some(encoding_val) = encoding {
            buf.extend_from_slice(b"\" encoding=\"");
            buf.extend_from_slice(encoding_val);
        }

        if let Some(standalone_val) = standalone {
            buf.extend_from_slice(b"\" standalone=\"");
            buf.extend_from_slice(standalone_val);
        }
        buf.push(b'"');

        BytesDecl {
            element: BytesStart::owned(buf, 3),
        }
    }

    /// Gets the decoder struct
    pub fn encoder(&self) -> Option<&'static Encoding> {
        self.encoding()
            .and_then(|e| e.ok())
            .and_then(|e| Encoding::for_label(&*e))
    }
}

/// A struct to manage `Event::End` events
#[derive(Clone, Debug)]
pub struct BytesEnd<'a> {
    name: Cow<'a, [u8]>,
}

impl<'a> BytesEnd<'a> {
    /// Creates a new `BytesEnd` borrowing a slice
    #[inline]
    pub fn borrowed(name: &'a [u8]) -> BytesEnd<'a> {
        BytesEnd {
            name: Cow::Borrowed(name),
        }
    }

    /// Creates a new `BytesEnd` owning its name
    #[inline]
    pub fn owned(name: Vec<u8>) -> BytesEnd<'static> {
        BytesEnd {
            name: Cow::Owned(name),
        }
    }

    /// Gets `BytesEnd` event name
    #[inline]
    pub fn name(&self) -> &[u8] {
        &*self.name
    }

    /// local name (excluding namespace) as &[u8] (without eventual attributes)
    /// returns the name() with any leading namespace removed (all content up to
    /// and including the first ':' character)
    #[inline]
    pub fn local_name(&self) -> &[u8] {
        if let Some(i) = self.name().iter().position(|b| *b == b':') {
            &self.name()[i + 1..]
        } else {
            self.name()
        }
    }
}

/// A struct to manage `Event::End` events
#[derive(Clone, Debug)]
pub struct BytesText<'a> {
    content: Cow<'a, [u8]>,
}

impl<'a> BytesText<'a> {
    /// Creates a new `BytesText` borrowing a slice
    #[inline]
    pub fn borrowed(content: &'a [u8]) -> BytesText<'a> {
        BytesText {
            content: Cow::Borrowed(content),
        }
    }

    /// Creates a new `BytesText` owning its name
    #[inline]
    pub fn owned(content: Vec<u8>) -> BytesText<'static> {
        BytesText {
            content: Cow::Owned(content),
        }
    }

    /// Creates a new `BytesText` from text
    #[inline]
    pub fn from_str<S: AsRef<str>>(text: S) -> BytesText<'static> {
        let bytes = escape(text.as_ref().as_bytes()).into_owned();
        BytesText {
            content: Cow::Owned(bytes),
        }
    }

    /// gets escaped content
    ///
    /// Searches for '&' into content and try to escape the coded character if possible
    /// returns Malformed error with index within element if '&' is not followed by ';'
    pub fn unescaped(&self) -> Result<Cow<[u8]>> {
        unescape(self).map_err(Error::EscapeError)
    }

    /// helper method to unescape then decode self using the reader encoding
    ///
    /// for performance reasons (could avoid allocating a `String`),
    /// it might be wiser to manually use
    /// 1. BytesText::unescaped()
    /// 2. Reader::decode(...)
    pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
        self.unescaped().map(|e| reader.decode(&*e).into_owned())
    }

    /// Gets escaped content
    ///
    /// Searches for any of `<, >, &, ', "` and xml escapes them.
    pub fn escaped(&self) -> &[u8] {
        self.content.as_ref()
    }
}

/// Event to interprete node as they are parsed
#[derive(Clone, Debug)]
pub enum Event<'a> {
    /// Start tag (with attributes) <...>
    Start(BytesStart<'a>),
    /// End tag </...>
    End(BytesEnd<'a>),
    /// Empty element tag (with attributes) <.../>
    Empty(BytesStart<'a>),
    /// Data between Start and End element
    Text(BytesText<'a>),
    /// Comment <!-- ... -->
    Comment(BytesText<'a>),
    /// CData <![CDATA[...]]>
    CData(BytesText<'a>),
    /// Xml declaration <?xml ...?>
    Decl(BytesDecl<'a>),
    /// Processing instruction <?...?>
    PI(BytesText<'a>),
    /// Doctype <!DOCTYPE...>
    DocType(BytesText<'a>),
    /// Eof of file event
    Eof,
}

impl<'a> Deref for BytesStart<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.buf
    }
}

impl<'a> Deref for BytesDecl<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.element
    }
}

impl<'a> Deref for BytesEnd<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.name
    }
}

impl<'a> Deref for BytesText<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.content
    }
}

impl<'a> Deref for Event<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        match *self {
            Event::Start(ref e) | Event::Empty(ref e) => &*e,
            Event::End(ref e) => &*e,
            Event::Text(ref e) => &*e,
            Event::Decl(ref e) => &*e,
            Event::PI(ref e) => &*e,
            Event::CData(ref e) => &*e,
            Event::Comment(ref e) => &*e,
            Event::DocType(ref e) => &*e,
            Event::Eof => &[],
        }
    }
}

impl<'a> AsRef<Event<'a>> for Event<'a> {
    fn as_ref(&self) -> &Event<'a> {
        self
    }
}

#[cfg(test)]
#[test]
fn local_name() {
    use std::str::from_utf8;
    let xml = r#"
        <foo:bus attr='bar'>foobusbar</foo:bus>
        <foo: attr='bar'>foobusbar</foo:>
        <:foo attr='bar'>foobusbar</:foo>
        <foo:bus:baz attr='bar'>foobusbar</foo:bus:baz>
        "#;
    let mut rdr = Reader::from_str(xml);
    let mut buf = Vec::new();
    let mut parsed_local_names = Vec::new();
    loop {
        match rdr.read_event(&mut buf).expect("unable to read xml event") {
            Event::Start(ref e) => parsed_local_names.push(
                from_utf8(e.local_name())
                    .expect("unable to build str from local_name")
                    .to_string(),
            ),
            Event::End(ref e) => parsed_local_names.push(
                from_utf8(e.local_name())
                    .expect("unable to build str from local_name")
                    .to_string(),
            ),
            Event::Eof => break,
            _ => {}
        }
    }
    assert_eq!(parsed_local_names[0], "bus".to_string());
    assert_eq!(parsed_local_names[1], "bus".to_string());
    assert_eq!(parsed_local_names[2], "".to_string());
    assert_eq!(parsed_local_names[3], "".to_string());
    assert_eq!(parsed_local_names[4], "foo".to_string());
    assert_eq!(parsed_local_names[5], "foo".to_string());
    assert_eq!(parsed_local_names[6], "bus:baz".to_string());
    assert_eq!(parsed_local_names[7], "bus:baz".to_string());
}
<commit_msg>add html_attributes in BytesStart<commit_after>//! A module to handle `Event` enumerator

pub mod attributes;

use std::borrow::Cow;
use std::str::from_utf8;
use std::ops::Deref;
use encoding_rs::Encoding;
use std::io::BufRead;

use escape::{escape, unescape};
use self::attributes::{Attribute, Attributes};
use errors::{Error, Result};
use reader::Reader;

use memchr;

/// A struct to manage `Event::Start` events
///
/// Provides in particular an iterator over attributes
#[derive(Clone, Debug)]
pub struct BytesStart<'a> {
    /// content of the element, before any utf8 conversion
    buf: Cow<'a, [u8]>,
    /// end of the element name, the name starts at that the start of `buf`
    name_len: usize,
}

impl<'a> BytesStart<'a> {
    /// Creates a new `BytesStart` from the given name.
    #[inline]
    pub fn borrowed(content: &'a [u8], name_len: usize) -> BytesStart<'a> {
        BytesStart {
            buf: Cow::Borrowed(content),
            name_len: name_len,
        }
    }

    /// Creates a new `BytesStart` from the given name. Owns its content
    #[inline]
    pub fn owned(content: Vec<u8>, name_len: usize) -> BytesStart<'static> {
        BytesStart {
            buf: Cow::Owned(content),
            name_len: name_len,
        }
    }

    /// Converts the event into an Owned event
    pub fn into_owned(self) -> BytesStart<'static> {
        BytesStart {
            buf: Cow::Owned(self.buf.into_owned()),
            name_len: self.name_len,
        }
    }

    /// Consumes self and adds attributes to this element from an iterator
    /// over (key, value) tuples.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn with_attributes<'b, I>(mut self, attributes: I) -> Self
    where
        I: IntoIterator,
        I::Item: Into<Attribute<'b>>,
    {
        self.extend_attributes(attributes);
        self
    }

    /// name as &[u8] (without eventual attributes)
    pub fn name(&self) -> &[u8] {
        &self.buf[..self.name_len]
    }

    /// local name (excluding namespace) as &[u8] (without eventual attributes)
    /// returns the name() with any leading namespace removed (all content up to
    /// and including the first ':' character)
    #[inline]
    pub fn local_name(&self) -> &[u8] {
        let name = self.name();
        memchr::memchr(b':', name).map_or(name, |i| &name[i + 1..])
    }

    /// gets unescaped content
    ///
    /// Searches for '&' into content and try to escape the coded character if possible
    /// returns Malformed error with index within element if '&' is not followed by ';'
    pub fn unescaped(&self) -> Result<Cow<[u8]>> {
        unescape(&*self.buf).map_err(Error::EscapeError)
    }

    /// gets attributes iterator
    pub fn attributes(&self) -> Attributes {
        Attributes::new(self, self.name_len)
    }

    /// gets attributes iterator with html syntax (no mandatory quote or =)
    pub fn html_attributes(&self) -> Attributes {
        Attributes::html(self, self.name_len)
    }

    /// extend the attributes of this element from an iterator over (key, value) tuples.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn extend_attributes<'b, I>(&mut self, attributes: I) -> &mut BytesStart<'a>
    where
        I: IntoIterator,
        I::Item: Into<Attribute<'b>>,
    {
        for attr in attributes {
            self.push_attribute(attr);
        }
        self
    }

    /// helper method to unescape then decode self using the reader encoding
    ///
    /// for performance reasons (could avoid allocating a `String`),
    /// it might be wiser to manually use
    /// 1. BytesStart::unescaped()
    /// 2. Reader::decode(...)
    pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
        self.unescaped().map(|e| reader.decode(&*e).into_owned())
    }

    /// Adds an attribute to this element from the given key and value.
    /// Key and value can be anything that implements the AsRef<[u8]> trait,
    /// like byte slices and strings.
    pub fn push_attribute<'b, A: Into<Attribute<'b>>>(&mut self, attr: A) {
        let a = attr.into();
        let bytes = self.buf.to_mut();
        bytes.push(b' ');
        bytes.extend_from_slice(a.key);
        bytes.extend_from_slice(b"=\"");
        bytes.extend_from_slice(&*a.value);
        bytes.push(b'"');
    }
}

/// Wrapper around `BytesElement` to parse/write `XmlDecl`
///
/// Postpone element parsing only when needed.
///
/// [W3C XML 1.1 Prolog and Document Type Delcaration](http://w3.org/TR/xml11/#sec-prolog-dtd)
#[derive(Clone, Debug)]
pub struct BytesDecl<'a> {
    element: BytesStart<'a>,
}

impl<'a> BytesDecl<'a> {
    /// Creates a `BytesDecl` from a `BytesStart`
    pub fn from_start(start: BytesStart<'a>) -> BytesDecl<'a> {
        BytesDecl { element: start }
    }

    /// Gets xml version, including quotes (' or ")
    pub fn version(&self) -> Result<Cow<[u8]>> {
        match self.element.attributes().next() {
            Some(Err(e)) => Err(e),
            Some(Ok(Attribute {
                key: b"version",
                value: v,
            })) => Ok(v),
            Some(Ok(a)) => {
                let found = from_utf8(a.key).map_err(Error::Utf8)?.to_string();
                Err(Error::XmlDeclWithoutVersion(Some(found)))
            },
            None => Err(Error::XmlDeclWithoutVersion(None)),
        }
    }

    /// Gets xml encoding, including quotes (' or ")
    pub fn encoding(&self) -> Option<Result<Cow<[u8]>>> {
        for a in self.element.attributes() {
            match a {
                Err(e) => return Some(Err(e)),
                Ok(Attribute {
                    key: b"encoding",
                    value: v,
                }) => return Some(Ok(v)),
                _ => (),
            }
        }
        None
    }

    /// Gets xml standalone, including quotes (' or ")
    pub fn standalone(&self) -> Option<Result<Cow<[u8]>>> {
        for a in self.element.attributes() {
            match a {
                Err(e) => return Some(Err(e)),
                Ok(Attribute {
                    key: b"standalone",
                    value: v,
                }) => return Some(Ok(v)),
                _ => (),
            }
        }
        None
    }

    /// Constructs a new `XmlDecl` from the (mandatory) _version_ (should be `1.0` or `1.1`),
    /// the optional _encoding_ (e.g., `UTF-8`) and the optional _standalone_ (`yes` or `no`)
    /// attribute.
    ///
    /// Does not escape any of its inputs. Always uses double quotes to wrap the attribute values.
    /// The caller is responsible for escaping attribute values. Shouldn't usually be relevant since
    /// the double quote character is not allowed in any of the attribute values.
    pub fn new(
        version: &[u8],
        encoding: Option<&[u8]>,
        standalone: Option<&[u8]>,
    ) -> BytesDecl<'static> {
        // Compute length of the buffer based on supplied attributes
        // ' encoding=""'   => 12
        let encoding_attr_len = if let Some(xs) = encoding {
            12 + xs.len()
        } else {
            0
        };
        // ' standalone=""' => 14
        let standalone_attr_len = if let Some(xs) = standalone {
            14 + xs.len()
        } else {
            0
        };
        // 'xml version=""' => 14
        let mut buf = Vec::with_capacity(14 + encoding_attr_len + standalone_attr_len);

        buf.extend_from_slice(b"xml version=\"");
        buf.extend_from_slice(version);

        if let Some(encoding_val) = encoding {
            buf.extend_from_slice(b"\" encoding=\"");
            buf.extend_from_slice(encoding_val);
        }

        if let Some(standalone_val) = standalone {
            buf.extend_from_slice(b"\" standalone=\"");
            buf.extend_from_slice(standalone_val);
        }
        buf.push(b'"');

        BytesDecl {
            element: BytesStart::owned(buf, 3),
        }
    }

    /// Gets the decoder struct
    pub fn encoder(&self) -> Option<&'static Encoding> {
        self.encoding()
            .and_then(|e| e.ok())
            .and_then(|e| Encoding::for_label(&*e))
    }
}

/// A struct to manage `Event::End` events
#[derive(Clone, Debug)]
pub struct BytesEnd<'a> {
    name: Cow<'a, [u8]>,
}

impl<'a> BytesEnd<'a> {
    /// Creates a new `BytesEnd` borrowing a slice
    #[inline]
    pub fn borrowed(name: &'a [u8]) -> BytesEnd<'a> {
        BytesEnd {
            name: Cow::Borrowed(name),
        }
    }

    /// Creates a new `BytesEnd` owning its name
    #[inline]
    pub fn owned(name: Vec<u8>) -> BytesEnd<'static> {
        BytesEnd {
            name: Cow::Owned(name),
        }
    }

    /// Gets `BytesEnd` event name
    #[inline]
    pub fn name(&self) -> &[u8] {
        &*self.name
    }

    /// local name (excluding namespace) as &[u8] (without eventual attributes)
    /// returns the name() with any leading namespace removed (all content up to
    /// and including the first ':' character)
    #[inline]
    pub fn local_name(&self) -> &[u8] {
        if let Some(i) = self.name().iter().position(|b| *b == b':') {
            &self.name()[i + 1..]
        } else {
            self.name()
        }
    }
}

/// A struct to manage `Event::End` events
#[derive(Clone, Debug)]
pub struct BytesText<'a> {
    content: Cow<'a, [u8]>,
}

impl<'a> BytesText<'a> {
    /// Creates a new `BytesText` borrowing a slice
    #[inline]
    pub fn borrowed(content: &'a [u8]) -> BytesText<'a> {
        BytesText {
            content: Cow::Borrowed(content),
        }
    }

    /// Creates a new `BytesText` owning its name
    #[inline]
    pub fn owned(content: Vec<u8>) -> BytesText<'static> {
        BytesText {
            content: Cow::Owned(content),
        }
    }

    /// Creates a new `BytesText` from text
    #[inline]
    pub fn from_str<S: AsRef<str>>(text: S) -> BytesText<'static> {
        let bytes = escape(text.as_ref().as_bytes()).into_owned();
        BytesText {
            content: Cow::Owned(bytes),
        }
    }

    /// gets escaped content
    ///
    /// Searches for '&' into content and try to escape the coded character if possible
    /// returns Malformed error with index within element if '&' is not followed by ';'
    pub fn unescaped(&self) -> Result<Cow<[u8]>> {
        unescape(self).map_err(Error::EscapeError)
    }

    /// helper method to unescape then decode self using the reader encoding
    ///
    /// for performance reasons (could avoid allocating a `String`),
    /// it might be wiser to manually use
    /// 1. BytesText::unescaped()
    /// 2. Reader::decode(...)
    pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
        self.unescaped().map(|e| reader.decode(&*e).into_owned())
    }

    /// Gets escaped content
    ///
    /// Searches for any of `<, >, &, ', "` and xml escapes them.
    pub fn escaped(&self) -> &[u8] {
        self.content.as_ref()
    }
}

/// Event to interprete node as they are parsed
#[derive(Clone, Debug)]
pub enum Event<'a> {
    /// Start tag (with attributes) <...>
    Start(BytesStart<'a>),
    /// End tag </...>
    End(BytesEnd<'a>),
    /// Empty element tag (with attributes) <.../>
    Empty(BytesStart<'a>),
    /// Data between Start and End element
    Text(BytesText<'a>),
    /// Comment <!-- ... -->
    Comment(BytesText<'a>),
    /// CData <![CDATA[...]]>
    CData(BytesText<'a>),
    /// Xml declaration <?xml ...?>
    Decl(BytesDecl<'a>),
    /// Processing instruction <?...?>
    PI(BytesText<'a>),
    /// Doctype <!DOCTYPE...>
    DocType(BytesText<'a>),
    /// Eof of file event
    Eof,
}

impl<'a> Deref for BytesStart<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.buf
    }
}

impl<'a> Deref for BytesDecl<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.element
    }
}

impl<'a> Deref for BytesEnd<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.name
    }
}

impl<'a> Deref for BytesText<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &*self.content
    }
}

impl<'a> Deref for Event<'a> {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        match *self {
            Event::Start(ref e) | Event::Empty(ref e) => &*e,
            Event::End(ref e) => &*e,
            Event::Text(ref e) => &*e,
            Event::Decl(ref e) => &*e,
            Event::PI(ref e) => &*e,
            Event::CData(ref e) => &*e,
            Event::Comment(ref e) => &*e,
            Event::DocType(ref e) => &*e,
            Event::Eof => &[],
        }
    }
}

impl<'a> AsRef<Event<'a>> for Event<'a> {
    fn as_ref(&self) -> &Event<'a> {
        self
    }
}

#[cfg(test)]
#[test]
fn local_name() {
    use std::str::from_utf8;
    let xml = r#"
        <foo:bus attr='bar'>foobusbar</foo:bus>
        <foo: attr='bar'>foobusbar</foo:>
        <:foo attr='bar'>foobusbar</:foo>
        <foo:bus:baz attr='bar'>foobusbar</foo:bus:baz>
        "#;
    let mut rdr = Reader::from_str(xml);
    let mut buf = Vec::new();
    let mut parsed_local_names = Vec::new();
    loop {
        match rdr.read_event(&mut buf).expect("unable to read xml event") {
            Event::Start(ref e) => parsed_local_names.push(
                from_utf8(e.local_name())
                    .expect("unable to build str from local_name")
                    .to_string(),
            ),
            Event::End(ref e) => parsed_local_names.push(
                from_utf8(e.local_name())
                    .expect("unable to build str from local_name")
                    .to_string(),
            ),
            Event::Eof => break,
            _ => {}
        }
    }
    assert_eq!(parsed_local_names[0], "bus".to_string());
    assert_eq!(parsed_local_names[1], "bus".to_string());
    assert_eq!(parsed_local_names[2], "".to_string());
    assert_eq!(parsed_local_names[3], "".to_string());
    assert_eq!(parsed_local_names[4], "foo".to_string());
    assert_eq!(parsed_local_names[5], "foo".to_string());
    assert_eq!(parsed_local_names[6], "bus:baz".to_string());
    assert_eq!(parsed_local_names[7], "bus:baz".to_string());
}
<|endoftext|>