<commit_before>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use sdl2::pixels::PixelFormatEnum;
use sdl2::rect::Rect;
use sdl2::render::TextureQuery;
use sdl2::surface::Surface;
use sdl2::image::LoadTexture;
use sdl2::render::{Texture, TextureCreator};
use sdl2::ttf::Font as Sdl2Font;
use sdl2::video::WindowContext;

use ron::de::from_reader;

use crate::ui::{Font, FontFace, ProtoSpriteConfig};
use crate::core::{SpriteConfig, SpriteSource, TextureMap};

pub struct AssetRepo<'a> {
    texture_creator: &'a TextureCreator<WindowContext>,

    pub texture: Option<Texture<'a>>,
    pub textures: HashMap<String, Texture<'a>>,
    pub fonts: [Option<Font<'a>>; 3],
}

impl<'a> AssetRepo<'a> {
    pub fn new(texture_creator: &'a TextureCreator<sdl2::video::WindowContext>) -> AssetRepo<'a> {
        Self {
            texture_creator,
            texture: None,
            textures: HashMap::new(),
            fonts: [None, None, None],
        }
    }

    pub fn init(mut self, image_path: &Path, font_path: &Path) -> Result<Self, String> {
        self.load_textures_from_path(image_path)?;

        let ttf_context = sdl2::ttf::init().map_err(|e| e.to_string())?;
        self.add_font(FontFace::Normal, ttf_context.load_font(font_path, 28)?)?;
        self.add_font(FontFace::Big, ttf_context.load_font(font_path, 48)?)?;
        self.add_font(FontFace::VeryBig, ttf_context.load_font(font_path, 96)?)?;

        Ok(self)
    }

    pub fn load_textures_from_path(self: &mut Self, path: &Path) -> Result<(), String> {
        for entry in path.read_dir().map_err(|e| e.to_string())? {
            let file_path = entry.map_err(|e| e.to_string())?.path();

            if let Some(ext) = file_path.clone().extension().and_then(|k| k.to_str()) {
                if let Some(key) = file_path.clone().file_stem().and_then(|k| k.to_str()) {
                    if ext == "png" {
                        // println!("[DEBUG] loading texture {:?}...", file_path);
                        let val = self.texture_creator.load_texture(file_path)?;
                        self.textures.insert(key.to_string(), val);
                    }
                }
            }
        }

        Ok(())
    }

    pub fn create_texture_from_path(
        self: &mut Self,
        path: &Path,
    ) -> Result<HashMap<String, SpriteConfig>, String> {
        let proto_sprites = read_proto_sprite_config(path);
        let pixel_format = self.texture_creator.default_pixel_format();
        let (sheet_width, sheet_height, sprite_positions) =
            compile_sprite_sheet_data(path, &proto_sprites, pixel_format)?;
        let mut tmp_cvs = Surface::new(sheet_width, sheet_height, pixel_format)?.into_canvas()?;
        let tmp_texture_creator = tmp_cvs.texture_creator();

        let texture_map = compile_texture_map(&proto_sprites, &sprite_positions);

        for (file, (x, y, w, h)) in sprite_positions.iter() {
            let file_path = path.join(Path::new(file));
            let texture = tmp_texture_creator.load_texture(file_path)?;
            tmp_cvs.copy(&texture, Rect::new(0, 0, *w, *h), Rect::new(*x, *y, *w, *h))?;
        }

        tmp_cvs.present();

        let sprite_sheet_texture = self
            .texture_creator
            .create_texture_from_surface(tmp_cvs.into_surface())
            .map_err(|e| e.to_string())?;

        self.texture = Some(sprite_sheet_texture);

        Ok(texture_map)
    }

    pub fn add_font(
        self: &mut Self,
        key: FontFace,
        font: Sdl2Font,
    ) -> Result<(), String> {
        self.fonts[key as usize] = Some(Font::from_font(self.texture_creator, font)?);
        Ok(())
    }
}

fn read_proto_sprite_config(path: &Path) -> Vec<(String, ProtoSpriteConfig)> {
    let p = path.join("sprites.ron");
    let f = match File::open(p) {
        Ok(result) => result,
        Err(e) => {
            panic!("Error opening proto sprite config file: {:?}", e);
        }
    };

    match from_reader(f) {
        Ok(result) => result,
        Err(e) => {
            panic!("Error parsing proto sprite config: {:?}", e);
        }
    }
}

fn compile_texture_map(
    proto_sprites: &Vec<(String, ProtoSpriteConfig)>,
    sprite_positions: &HashMap<String, (i32, i32, u32, u32)>,
) -> TextureMap {
    let mut texture_map = HashMap::new();
    for (key, proto_cfg) in proto_sprites.iter() {
        if proto_cfg.files.len() == 1 {
            // a single image without animation
            let (x, y, w, h) = sprite_positions.get(&proto_cfg.files[0]).unwrap();
            texture_map.insert(
                key.to_string(),
                SpriteConfig {
                    source: SpriteSource::Static(*x, *y),
                    dim: (*w, *h),
                    offset: proto_cfg.offset,
                    alpha: proto_cfg.alpha.unwrap_or(255),
                    scale: 1.0,
                },
            );
        } else {
            let (_, _, w, h) = sprite_positions.get(&proto_cfg.files[0]).unwrap();
            let mut frame_positions = vec![];

            for f in proto_cfg.files.iter() {
                let (x, y, _, _) = sprite_positions.get(f).unwrap();
                frame_positions.push((*x, *y));
            }

            texture_map.insert(
                key.to_string(),
                SpriteConfig {
                    source: SpriteSource::SimpleAnimation(
                        proto_cfg.frame_durration.unwrap_or(50),
                        frame_positions,
                    ),
                    dim: (*w, *h),
                    offset: proto_cfg.offset,
                    alpha: proto_cfg.alpha.unwrap_or(255),
                    scale: 1.0,
                },
            );
        }
    }

    texture_map
}

fn compile_sprite_sheet_data(
    path: &Path,
    proto_sprites: &Vec<(String, ProtoSpriteConfig)>,
    pixel_format_enum: PixelFormatEnum,
) -> Result<(u32, u32, HashMap<String, (i32, i32, u32, u32)>), String> {
    let (mut x, mut y) = (0, 0);
    let mut line_height = 0;
    let max_width = 2048;
    let mut total_height = 0;
    let tmp_cvs = Surface::new(max_width, 2048, pixel_format_enum)?.into_canvas()?;
    let tmp_texture_creator = tmp_cvs.texture_creator();
    let mut sprite_position_cache: HashMap<String, (i32, i32, u32, u32)> = HashMap::new();

    for (_, proto_cfg) in proto_sprites.iter() {
        for file in proto_cfg.files.iter() {
            if sprite_position_cache.contains_key(file) {
                continue;
            }

            let file_path = path.join(Path::new(file));
            let texture = tmp_texture_creator.load_texture(file_path)?;
            let TextureQuery { width, height, .. } = texture.query();

            line_height = std::cmp::max(line_height, height);

            sprite_position_cache.insert(file.to_string(), (x, y, width, height));

            if x as u32 + width < max_width {
                x = x + width as i32;
            } else {
                // new line
                x = 0;
                y = y + line_height as i32;
                total_height = total_height + line_height;
                line_height = 0;
            }
        }
    }

    total_height = total_height + line_height; // add last line

    // println!(
    //     "sheet width: {}, sheet height: {}, sprite positions: {:?}",
    //     max_width, total_height, sprite_position_cache
    // );
    Ok((max_width, total_height, sprite_position_cache))
}
<commit_msg>Fixed bug in sprite sheet generation<commit_after>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use sdl2::pixels::PixelFormatEnum;
use sdl2::rect::Rect;
use sdl2::render::TextureQuery;
use sdl2::surface::Surface;
use sdl2::image::LoadTexture;
use sdl2::render::{Texture, TextureCreator};
use sdl2::ttf::Font as Sdl2Font;
use sdl2::video::WindowContext;

use ron::de::from_reader;

use crate::ui::{Font, FontFace, ProtoSpriteConfig};
use crate::core::{SpriteConfig, SpriteSource, TextureMap};

pub struct AssetRepo<'a> {
    texture_creator: &'a TextureCreator<WindowContext>,

    pub texture: Option<Texture<'a>>,
    pub textures: HashMap<String, Texture<'a>>,
    pub fonts: [Option<Font<'a>>; 3],
}

impl<'a> AssetRepo<'a> {
    pub fn new(texture_creator: &'a TextureCreator<sdl2::video::WindowContext>) -> AssetRepo<'a> {
        Self {
            texture_creator,
            texture: None,
            textures: HashMap::new(),
            fonts: [None, None, None],
        }
    }

    pub fn init(mut self, image_path: &Path, font_path: &Path) -> Result<Self, String> {
        self.load_textures_from_path(image_path)?;

        let ttf_context = sdl2::ttf::init().map_err(|e| e.to_string())?;
        self.add_font(FontFace::Normal, ttf_context.load_font(font_path, 28)?)?;
        self.add_font(FontFace::Big, ttf_context.load_font(font_path, 48)?)?;
        self.add_font(FontFace::VeryBig, ttf_context.load_font(font_path, 96)?)?;

        Ok(self)
    }

    pub fn load_textures_from_path(self: &mut Self, path: &Path) -> Result<(), String> {
        for entry in path.read_dir().map_err(|e| e.to_string())? {
            let file_path = entry.map_err(|e| e.to_string())?.path();

            if let Some(ext) = file_path.clone().extension().and_then(|k| k.to_str()) {
                if let Some(key) = file_path.clone().file_stem().and_then(|k| k.to_str()) {
                    if ext == "png" {
                        // println!("[DEBUG] loading texture {:?}...", file_path);
                        let val = self.texture_creator.load_texture(file_path)?;
                        self.textures.insert(key.to_string(), val);
                    }
                }
            }
        }

        Ok(())
    }

    pub fn create_texture_from_path(
        self: &mut Self,
        path: &Path,
    ) -> Result<HashMap<String, SpriteConfig>, String> {
        let proto_sprites = read_proto_sprite_config(path);
        let pixel_format = self.texture_creator.default_pixel_format();
        let (sheet_width, sheet_height, sprite_positions) =
            compile_sprite_sheet_data(path, &proto_sprites, pixel_format)?;
        let mut tmp_cvs = Surface::new(sheet_width, sheet_height, pixel_format)?.into_canvas()?;
        let tmp_texture_creator = tmp_cvs.texture_creator();

        let texture_map = compile_texture_map(&proto_sprites, &sprite_positions);

        for (file, (x, y, w, h)) in sprite_positions.iter() {
            let file_path = path.join(Path::new(file));
            let texture = tmp_texture_creator.load_texture(file_path)?;
            tmp_cvs.copy(&texture, Rect::new(0, 0, *w, *h), Rect::new(*x, *y, *w, *h))?;
        }

        tmp_cvs.present();

        let sprite_sheet_texture = self
            .texture_creator
            .create_texture_from_surface(tmp_cvs.into_surface())
            .map_err(|e| e.to_string())?;

        self.texture = Some(sprite_sheet_texture);

        Ok(texture_map)
    }

    pub fn add_font(
        self: &mut Self,
        key: FontFace,
        font: Sdl2Font,
    ) -> Result<(), String> {
        self.fonts[key as usize] = Some(Font::from_font(self.texture_creator, font)?);
        Ok(())
    }
}

fn read_proto_sprite_config(path: &Path) -> Vec<(String, ProtoSpriteConfig)> {
    let p = path.join("sprites.ron");
    let f = match File::open(p) {
        Ok(result) => result,
        Err(e) => {
            panic!("Error opening proto sprite config file: {:?}", e);
        }
    };

    match from_reader(f) {
        Ok(result) => result,
        Err(e) => {
            panic!("Error parsing proto sprite config: {:?}", e);
        }
    }
}

fn compile_texture_map(
    proto_sprites: &Vec<(String, ProtoSpriteConfig)>,
    sprite_positions: &HashMap<String, (i32, i32, u32, u32)>,
) -> TextureMap {
    let mut texture_map = HashMap::new();
    for (key, proto_cfg) in proto_sprites.iter() {
        if proto_cfg.files.len() == 1 {
            // a single image without animation
            let (x, y, w, h) = sprite_positions.get(&proto_cfg.files[0]).unwrap();
            texture_map.insert(
                key.to_string(),
                SpriteConfig {
                    source: SpriteSource::Static(*x, *y),
                    dim: (*w, *h),
                    offset: proto_cfg.offset,
                    alpha: proto_cfg.alpha.unwrap_or(255),
                    scale: 1.0,
                },
            );
        } else {
            let (_, _, w, h) = sprite_positions.get(&proto_cfg.files[0]).unwrap();
            let mut frame_positions = vec![];

            for f in proto_cfg.files.iter() {
                let (x, y, _, _) = sprite_positions.get(f).unwrap();
                frame_positions.push((*x, *y));
            }

            texture_map.insert(
                key.to_string(),
                SpriteConfig {
                    source: SpriteSource::SimpleAnimation(
                        proto_cfg.frame_durration.unwrap_or(50),
                        frame_positions,
                    ),
                    dim: (*w, *h),
                    offset: proto_cfg.offset,
                    alpha: proto_cfg.alpha.unwrap_or(255),
                    scale: 1.0,
                },
            );
        }
    }

    texture_map
}

fn compile_sprite_sheet_data(
    path: &Path,
    proto_sprites: &Vec<(String, ProtoSpriteConfig)>,
    pixel_format_enum: PixelFormatEnum,
) -> Result<(u32, u32, HashMap<String, (i32, i32, u32, u32)>), String> {
    let (mut x, mut y) = (0, 0);
    let mut line_height = 0;
    let max_width = 2048;
    let mut total_height = 0;
    let tmp_cvs = Surface::new(max_width, 2048, pixel_format_enum)?.into_canvas()?;
    let tmp_texture_creator = tmp_cvs.texture_creator();
    let mut sprite_position_cache: HashMap<String, (i32, i32, u32, u32)> = HashMap::new();

    for (_, proto_cfg) in proto_sprites.iter() {
        for file in proto_cfg.files.iter() {
            if sprite_position_cache.contains_key(file) {
                continue;
            }

            let file_path = path.join(Path::new(file));
            let texture = tmp_texture_creator.load_texture(file_path)?;
            let TextureQuery { width, height, .. } = texture.query();

            line_height = std::cmp::max(line_height, height);

            if x as u32 + width > max_width {
                // new line
                x = 0;
                y = y + line_height as i32;
                total_height = total_height + line_height;
                line_height = 0;
            }

            sprite_position_cache.insert(file.to_string(), (x, y, width, height));

            x += width as i32;
        }
    }

    total_height = total_height + line_height; // add last line

    // println!(
    //     "sheet width: {}, sheet height: {}, sprite positions: {:?}",
    //     max_width, total_height, sprite_position_cache
    // );
    Ok((max_width, total_height, sprite_position_cache))
}
<|endoftext|>


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

use std::ops::Deref;
use std::ops::DerefMut;
use std::io::{self, Write};
use std::net::{ToSocketAddrs, TcpStream};
use time;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use rand::{self, ThreadRng};
use protobuf::Message;

pub use log::LogLevelFilter;
use log::{self, Log, LogMetadata, LogRecord, SetLoggerError};

pub mod codec;
#[macro_use]
pub mod macros;
pub mod logger;
// TODO: add #[cfg(test)] if we get it compiled.
pub mod panic_hook;

pub fn init_log(level: LogLevelFilter) -> Result<(), SetLoggerError> {
    log::set_logger(|filter| {
        filter.set(level);
        Box::new(DefaultLogger { level: level })
    })
}

struct DefaultLogger {
    level: LogLevelFilter,
}

impl Log for DefaultLogger {
    fn enabled(&self, meta: &LogMetadata) -> bool {
        meta.level() <= self.level
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let t = time::now();
            // TODO allow formatter to be configurable.
            let _ = write!(io::stderr(),
                           "{},{:03} {}:{} - {:5} - {}\n",
                           time::strftime("%Y-%m-%d %H:%M:%S", &t).unwrap(),
                           t.tm_nsec / 1000_000,
                           record.location().file().rsplit('/').nth(0).unwrap(),
                           record.location().line(),
                           record.level(),
                           record.args());
        }
    }
}

pub fn limit_size<T: Message + Clone>(entries: &[T], max: u64) -> Vec<T> {
    if entries.len() == 0 {
        return entries.to_vec();
    }

    let mut size = Message::compute_size(&entries[0]) as u64;
    let mut limit = 1usize;
    while limit < entries.len() {
        size += Message::compute_size(&entries[limit]) as u64;
        if size > max {
            break;
        }
        limit += 1;
    }
    entries[..limit].to_vec()
}

pub struct DefaultRng {
    rng: ThreadRng,
}

impl DefaultRng {
    fn new() -> DefaultRng {
        DefaultRng { rng: rand::thread_rng() }
    }
}

impl Default for DefaultRng {
    fn default() -> DefaultRng {
        DefaultRng::new()
    }
}

impl Deref for DefaultRng {
    type Target = ThreadRng;

    fn deref(&self) -> &ThreadRng {
        &self.rng
    }
}

impl DerefMut for DefaultRng {
    fn deref_mut(&mut self) -> &mut ThreadRng {
        &mut self.rng
    }
}

/// A handy shortcut to replace `RwLock` write/read().unwrap() pattern to
/// shortcut wl and rl.
pub trait HandyRwLock<T> {
    fn wl(&self) -> RwLockWriteGuard<T>;
    fn rl(&self) -> RwLockReadGuard<T>;
}

impl<T> HandyRwLock<T> for RwLock<T> {
    fn wl(&self) -> RwLockWriteGuard<T> {
        self.write().unwrap()
    }

    fn rl(&self) -> RwLockReadGuard<T> {
        self.read().unwrap()
    }
}

pub fn make_std_tcp_conn<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> {
    let stream = try!(TcpStream::connect(addr));
    try!(stream.set_nodelay(false));
    Ok(stream)
}
<commit_msg>util: set_nodelay on tcp connection by default.<commit_after>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

use std::ops::Deref;
use std::ops::DerefMut;
use std::io::{self, Write};
use std::net::{ToSocketAddrs, TcpStream};
use time;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use rand::{self, ThreadRng};
use protobuf::Message;

pub use log::LogLevelFilter;
use log::{self, Log, LogMetadata, LogRecord, SetLoggerError};

pub mod codec;
#[macro_use]
pub mod macros;
pub mod logger;
// TODO: add #[cfg(test)] if we get it compiled.
pub mod panic_hook;

pub fn init_log(level: LogLevelFilter) -> Result<(), SetLoggerError> {
    log::set_logger(|filter| {
        filter.set(level);
        Box::new(DefaultLogger { level: level })
    })
}

struct DefaultLogger {
    level: LogLevelFilter,
}

impl Log for DefaultLogger {
    fn enabled(&self, meta: &LogMetadata) -> bool {
        meta.level() <= self.level
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let t = time::now();
            // TODO allow formatter to be configurable.
            let _ = write!(io::stderr(),
                           "{},{:03} {}:{} - {:5} - {}\n",
                           time::strftime("%Y-%m-%d %H:%M:%S", &t).unwrap(),
                           t.tm_nsec / 1000_000,
                           record.location().file().rsplit('/').nth(0).unwrap(),
                           record.location().line(),
                           record.level(),
                           record.args());
        }
    }
}

pub fn limit_size<T: Message + Clone>(entries: &[T], max: u64) -> Vec<T> {
    if entries.len() == 0 {
        return entries.to_vec();
    }

    let mut size = Message::compute_size(&entries[0]) as u64;
    let mut limit = 1usize;
    while limit < entries.len() {
        size += Message::compute_size(&entries[limit]) as u64;
        if size > max {
            break;
        }
        limit += 1;
    }
    entries[..limit].to_vec()
}

pub struct DefaultRng {
    rng: ThreadRng,
}

impl DefaultRng {
    fn new() -> DefaultRng {
        DefaultRng { rng: rand::thread_rng() }
    }
}

impl Default for DefaultRng {
    fn default() -> DefaultRng {
        DefaultRng::new()
    }
}

impl Deref for DefaultRng {
    type Target = ThreadRng;

    fn deref(&self) -> &ThreadRng {
        &self.rng
    }
}

impl DerefMut for DefaultRng {
    fn deref_mut(&mut self) -> &mut ThreadRng {
        &mut self.rng
    }
}

/// A handy shortcut to replace `RwLock` write/read().unwrap() pattern to
/// shortcut wl and rl.
pub trait HandyRwLock<T> {
    fn wl(&self) -> RwLockWriteGuard<T>;
    fn rl(&self) -> RwLockReadGuard<T>;
}

impl<T> HandyRwLock<T> for RwLock<T> {
    fn wl(&self) -> RwLockWriteGuard<T> {
        self.write().unwrap()
    }

    fn rl(&self) -> RwLockReadGuard<T> {
        self.read().unwrap()
    }
}

pub fn make_std_tcp_conn<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> {
    let stream = try!(TcpStream::connect(addr));
    try!(stream.set_nodelay(true));
    Ok(stream)
}
<|endoftext|>


<commit_before>//! Interfaces for set-theoretic operations and relations on arbitrary types.
//!
//! These traits define interfaces for set-like operations on pre-defined
//! types, and when implemented for a type allow use of several
//! included algorithms.

use std::iter::Iterator;

/// Trait for checking if a set contains the given element.
pub trait Contains<E>
    where E: PartialEq
{
    /// Check if the set contains the given element.
    fn contains(&self, element: E) -> bool;
}

/// Interface used to check if a set contains zero elements.
pub trait IsEmpty {
    /// Check if the set is empty.
    fn is_empty(&self) -> bool;
}

/// Trait for checking if a particular set's members are all contained in
/// another set.
pub trait IsSubsetOf<S> {
    /// Determine whether all elements in this are also elements of the
    /// given set.
    fn is_subset_of(&self, other: &S) -> bool;
}

/// Trait for checking if a particular set contains all members of another set.
///
/// `IsSupersetOf<S>` is automatically defined for all types `T` where
/// `S: [IsSubsetOf](trait.IsSubsetOf.html)<T>`; therefore you should prefer to
/// implement `IsSubsetOf` instead.
pub trait IsSupersetOf<S> {
    /// Check if this set contains all elements of the given set.
    fn is_superset_of(&self, other: &S) -> bool;
}

impl<T, S> IsSupersetOf<S> for T
    where S: IsSubsetOf<T>
{
    #[inline(always)]
    fn is_superset_of(&self, other: &S) -> bool {
        other.is_subset_of(self)
    }
}

/// Trait for set types whose elements can be accessed by iteration.
pub trait IterableSet<'a, E: 'a> {
    /// Iterator type used when iterating over the set's elements.
    type Iter: Iterator<Item=&'a E>;

    /// Fetch an iterator over (references to) the members of the set.
    fn iter(&self) -> Self::Iter;
}

/// Trait for set types that support the [absolute
/// complement][absolute-complement] operation.
///
/// [absolute-complement]: https://en.wikipedia.org/wiki/Complement_(set_theory)#Absolute_complement
pub trait Complement {
    /// Type returned by an instance's `complement()` method.
    type Output;

    /// Calculate the absolute complement of the set.
    fn complement(&self) -> Self::Output;
}

/// Trait for set types that support the [set-theoretic difference][setdiff]
/// operation, which is also called the relative complement.
///
/// [setdiff]: https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement
pub trait Difference<S> {
    /// Type of the result of computing this difference.
    type Output;
    /// Calculate the set-theoretic difference of this set with the one given.
    fn difference(self, other: &S) -> Self::Output;
}

/// Utility for working with multiple sets as an aggregate.
pub struct AggregateSet<'a, S: 'a> {
    sets: Vec<&'a S>
}

impl<'a, S: 'a, E> Contains<E> for AggregateSet<'a, S>
    where S: Contains<E>, E: Copy + PartialEq
{
    #[inline]
    fn contains(&self, e: E) -> bool {
        self.sets.iter().any(|set| set.contains(e))
    }
}

impl<'a, S: 'a> IsSubsetOf<AggregateSet<'a, S>> for S
    where S: IsEmpty + IsSubsetOf<S> + Subtract<S> + Clone
{
    fn is_subset_of(&self, agg: &AggregateSet<'a, S>) -> bool {
        // If this set is empty, or is completely contained by any of the
        // aggregate's members, it's a subset.
        if self.is_empty() || agg.sets.iter().any(|set| self.is_subset_of(set)) {
            return true
        }

        // Otherwise we'll go the brute-force route: make a clone of `self`,
        // then subtract each member set from the clone until either the clone
        // is empty (=> this set is a subset of the aggregate) or we run out of
        // members (=> not a subset).
        let mut iter = agg.sets.iter();
        let mut diff = self.clone();

        while let Some(set) = iter.next() {
            if diff.subtract(set).is_empty() {
                return true;
            }
        }

        false
    }
}
<commit_msg>util/set: remove unused `AggregateSet` struct<commit_after>//! Interfaces for set-theoretic operations and relations on arbitrary types.
//!
//! These traits define interfaces for set-like operations on pre-defined
//! types, and when implemented for a type allow use of several
//! included algorithms.

use std::iter::Iterator;

/// Trait for checking if a set contains the given element.
pub trait Contains<E>
    where E: PartialEq
{
    /// Check if the set contains the given element.
    fn contains(&self, element: E) -> bool;
}

/// Interface used to check if a set contains zero elements.
pub trait IsEmpty {
    /// Check if the set is empty.
    fn is_empty(&self) -> bool;
}

/// Trait for checking if a particular set's members are all contained in
/// another set.
pub trait IsSubsetOf<S> {
    /// Determine whether all elements in this are also elements of the
    /// given set.
    fn is_subset_of(&self, other: &S) -> bool;
}

/// Trait for checking if a particular set contains all members of another set.
///
/// `IsSupersetOf<S>` is automatically defined for all types `T` where
/// `S: [IsSubsetOf](trait.IsSubsetOf.html)<T>`; therefore you should prefer to
/// implement `IsSubsetOf` instead.
pub trait IsSupersetOf<S> {
    /// Check if this set contains all elements of the given set.
    fn is_superset_of(&self, other: &S) -> bool;
}

impl<T, S> IsSupersetOf<S> for T
    where S: IsSubsetOf<T>
{
    #[inline(always)]
    fn is_superset_of(&self, other: &S) -> bool {
        other.is_subset_of(self)
    }
}

/// Trait for set types whose elements can be accessed by iteration.
pub trait IterableSet<'a, E: 'a> {
    /// Iterator type used when iterating over the set's elements.
    type Iter: Iterator<Item=&'a E>;

    /// Fetch an iterator over (references to) the members of the set.
    fn iter(&self) -> Self::Iter;
}

/// Trait for set types that support the [absolute
/// complement][absolute-complement] operation.
///
/// [absolute-complement]: https://en.wikipedia.org/wiki/Complement_(set_theory)#Absolute_complement
pub trait Complement {
    /// Type returned by an instance's `complement()` method.
    type Output;

    /// Calculate the absolute complement of the set.
    fn complement(&self) -> Self::Output;
}

/// Trait for set types that support the [set-theoretic difference][setdiff]
/// operation, which is also called the relative complement.
///
/// [setdiff]: https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement
pub trait Difference<S> {
    /// Type of the result of computing this difference.
    type Output;
    /// Calculate the set-theoretic difference of this set with the one given.
    fn difference(self, other: &S) -> Self::Output;
}
<|endoftext|>


<commit_before>use bluetooth_session::BluetoothSession;
use bluetooth_utils;
use dbus::{BusType, Connection, Message, MessageItem, MessageItemArray, Signature};

use std::error::Error;

static SERVICE_NAME: &'static str = "org.bluez";
static GATT_CHARACTERISTIC_INTERFACE: &'static str = "org.bluez.GattCharacteristic1";

#[derive(Clone, Debug)]
pub struct BluetoothGATTCharacteristic<'a> {
    object_path: String,
    session: &'a BluetoothSession,
}

impl<'a> BluetoothGATTCharacteristic<'a> {
    pub fn new(session: &'a BluetoothSession, object_path: String) -> BluetoothGATTCharacteristic {
        BluetoothGATTCharacteristic {
            object_path: object_path,
            session: session,
        }
    }

    pub fn get_id(&self) -> String {
        self.object_path.clone()
    }

    fn get_property(&self, prop: &str) -> Result<MessageItem, Box<Error>> {
        bluetooth_utils::get_property(
            self.session.get_connection(),
            GATT_CHARACTERISTIC_INTERFACE,
            &self.object_path,
            prop,
        )
    }

    fn call_method(&self, method: &str, param: Option<&[MessageItem]>) -> Result<(), Box<Error>> {
        bluetooth_utils::call_method(
            self.session.get_connection(),
            GATT_CHARACTERISTIC_INTERFACE,
            &self.object_path,
            method,
            param,
        )
    }

    /*
     * Properties
     */

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n114
    pub fn get_uuid(&self) -> Result<String, Box<Error>> {
        let uuid = try!(self.get_property("UUID"));
        Ok(String::from(uuid.inner::<&str>().unwrap()))
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n118
    pub fn get_service(&self) -> Result<String, Box<Error>> {
        let service = try!(self.get_property("Service"));
        Ok(String::from(service.inner::<&str>().unwrap()))
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n123
    pub fn get_value(&self) -> Result<Vec<u8>, Box<Error>> {
        let value = try!(self.get_property("Value"));
        let z: &[MessageItem] = value.inner().unwrap();
        let mut v: Vec<u8> = Vec::new();
        for y in z {
            v.push(y.inner::<u8>().unwrap());
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n130
    pub fn is_notifying(&self) -> Result<bool, Box<Error>> {
        let notifying = try!(self.get_property("Notifying"));
        Ok(notifying.inner::<bool>().unwrap())
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n135
    pub fn get_flags(&self) -> Result<Vec<String>, Box<Error>> {
        let flags = try!(self.get_property("Flags"));
        let z: &[MessageItem] = flags.inner().unwrap();
        let mut v: Vec<String> = Vec::new();
        for y in z {
            v.push(String::from(y.inner::<&str>().unwrap()));
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n156
    pub fn get_gatt_descriptors(&self) -> Result<Vec<String>, Box<Error>> {
        bluetooth_utils::list_descriptors(self.session.get_connection(), &self.object_path)
    }

    /*
     * Methods
     */

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n72
    pub fn read_value(&self, offset: Option<u16>) -> Result<Vec<u8>, Box<Error>> {
        let c = try!(Connection::get_private(BusType::System));
        let mut m = try!(Message::new_method_call(
            SERVICE_NAME,
            &self.object_path,
            GATT_CHARACTERISTIC_INTERFACE,
            "ReadValue"
        ));
        m.append_items(&[MessageItem::Array(
            MessageItemArray::new(
                match offset {
                    Some(o) => vec![MessageItem::DictEntry(
                        Box::new("offset".into()),
                        Box::new(MessageItem::Variant(Box::new(o.into()))),
                    )],
                    None => vec![],
                },
                Signature::from("a{sv}"),
            ).unwrap(),
        )]);
        let reply = try!(c.send_with_reply_and_block(m, 1000));
        let items: MessageItem = reply.get1().unwrap();
        let z: &[MessageItem] = items.inner().unwrap();
        let mut v: Vec<u8> = Vec::new();
        for i in z {
            v.push(i.inner::<u8>().unwrap());
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n84
    pub fn write_value(&self, values: Vec<u8>, offset: Option<u16>) -> Result<(), Box<Error>> {
        let values_msgs = {
            let mut res: Vec<MessageItem> = Vec::new();
            for v in values {
                res.push(v.into());
            }
            res
        };
        self.call_method(
            "WriteValue",
            Some(&[
                MessageItem::new_array(values_msgs).unwrap(),
                MessageItem::Array(
                    MessageItemArray::new(
                        match offset {
                            Some(o) => vec![MessageItem::DictEntry(
                                Box::new("offset".into()),
                                Box::new(MessageItem::Variant(Box::new(o.into()))),
                            )],
                            None => vec![],
                        },
                        Signature::from("a{sv}"),
                    ).unwrap(),
                ),
            ]),
        )
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n96
    pub fn start_notify(&self) -> Result<(), Box<Error>> {
        self.call_method("StartNotify", None)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n105
    pub fn stop_notify(&self) -> Result<(), Box<Error>> {
        self.call_method("StopNotify", None)
    }
}
<commit_msg>implemented acquire_write and acquire_notify<commit_after>use bluetooth_session::BluetoothSession;
use bluetooth_utils;
use dbus::{BusType, Connection, Message, MessageItem, MessageItemArray, OwnedFd, Signature};

use std::error::Error;

static SERVICE_NAME: &'static str = "org.bluez";
static GATT_CHARACTERISTIC_INTERFACE: &'static str = "org.bluez.GattCharacteristic1";

#[derive(Clone, Debug)]
pub struct BluetoothGATTCharacteristic<'a> {
    object_path: String,
    session: &'a BluetoothSession,
}

impl<'a> BluetoothGATTCharacteristic<'a> {
    pub fn new(session: &'a BluetoothSession, object_path: String) -> BluetoothGATTCharacteristic {
        BluetoothGATTCharacteristic {
            object_path: object_path,
            session: session,
        }
    }

    pub fn get_id(&self) -> String {
        self.object_path.clone()
    }

    fn get_property(&self, prop: &str) -> Result<MessageItem, Box<Error>> {
        bluetooth_utils::get_property(
            self.session.get_connection(),
            GATT_CHARACTERISTIC_INTERFACE,
            &self.object_path,
            prop,
        )
    }

    fn call_method(&self, method: &str, param: Option<&[MessageItem]>) -> Result<(), Box<Error>> {
        bluetooth_utils::call_method(
            self.session.get_connection(),
            GATT_CHARACTERISTIC_INTERFACE,
            &self.object_path,
            method,
            param,
        )
    }

    /*
     * Properties
     */

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n114
    pub fn get_uuid(&self) -> Result<String, Box<Error>> {
        let uuid = try!(self.get_property("UUID"));
        Ok(String::from(uuid.inner::<&str>().unwrap()))
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n118
    pub fn get_service(&self) -> Result<String, Box<Error>> {
        let service = try!(self.get_property("Service"));
        Ok(String::from(service.inner::<&str>().unwrap()))
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n123
    pub fn get_value(&self) -> Result<Vec<u8>, Box<Error>> {
        let value = try!(self.get_property("Value"));
        let z: &[MessageItem] = value.inner().unwrap();
        let mut v: Vec<u8> = Vec::new();
        for y in z {
            v.push(y.inner::<u8>().unwrap());
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n130
    pub fn is_notifying(&self) -> Result<bool, Box<Error>> {
        let notifying = try!(self.get_property("Notifying"));
        Ok(notifying.inner::<bool>().unwrap())
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n135
    pub fn get_flags(&self) -> Result<Vec<String>, Box<Error>> {
        let flags = try!(self.get_property("Flags"));
        let z: &[MessageItem] = flags.inner().unwrap();
        let mut v: Vec<String> = Vec::new();
        for y in z {
            v.push(String::from(y.inner::<&str>().unwrap()));
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n156
    pub fn get_gatt_descriptors(&self) -> Result<Vec<String>, Box<Error>> {
        bluetooth_utils::list_descriptors(self.session.get_connection(), &self.object_path)
    }

    /*
     * Methods
     */

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n72
    pub fn read_value(&self, offset: Option<u16>) -> Result<Vec<u8>, Box<Error>> {
        let c = try!(Connection::get_private(BusType::System));
        let mut m = try!(Message::new_method_call(
            SERVICE_NAME,
            &self.object_path,
            GATT_CHARACTERISTIC_INTERFACE,
            "ReadValue"
        ));
        m.append_items(&[MessageItem::Array(
            MessageItemArray::new(
                match offset {
                    Some(o) => vec![MessageItem::DictEntry(
                        Box::new("offset".into()),
                        Box::new(MessageItem::Variant(Box::new(o.into()))),
                    )],
                    None => vec![],
                },
                Signature::from("a{sv}"),
            ).unwrap(),
        )]);
        let reply = try!(c.send_with_reply_and_block(m, 1000));
        let items: MessageItem = reply.get1().unwrap();
        let z: &[MessageItem] = items.inner().unwrap();
        let mut v: Vec<u8> = Vec::new();
        for i in z {
            v.push(i.inner::<u8>().unwrap());
        }
        Ok(v)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n84
    pub fn write_value(&self, values: Vec<u8>, offset: Option<u16>) -> Result<(), Box<Error>> {
        let values_msgs = {
            let mut res: Vec<MessageItem> = Vec::new();
            for v in values {
                res.push(v.into());
            }
            res
        };
        self.call_method(
            "WriteValue",
            Some(&[
                MessageItem::new_array(values_msgs).unwrap(),
                MessageItem::Array(
                    MessageItemArray::new(
                        match offset {
                            Some(o) => vec![MessageItem::DictEntry(
                                Box::new("offset".into()),
                                Box::new(MessageItem::Variant(Box::new(o.into()))),
                            )],
                            None => vec![],
                        },
                        Signature::from("a{sv}"),
                    ).unwrap(),
                ),
            ]),
        )
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n96
    pub fn start_notify(&self) -> Result<(), Box<Error>> {
        self.call_method("StartNotify", None)
    }

    // http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/gatt-api.txt#n105
    pub fn stop_notify(&self) -> Result<(), Box<Error>> {
        self.call_method("StopNotify", None)
    }

    pub fn acquire_notify(&self) -> Result<(OwnedFd, u16), Box<Error>> {
        let mut m = Message::new_method_call(
            SERVICE_NAME,
            &self.object_path,
            GATT_CHARACTERISTIC_INTERFACE,
            "AcquireNotify",
        )?;
        m.append_items(&[MessageItem::Array(
            MessageItemArray::new(vec![], Signature::from("a{sv}")).unwrap(),
        )]);
        let reply = self
            .session
            .get_connection()
            .send_with_reply_and_block(m, 1000)?;
        let (opt_fd, opt_mtu) = reply.get2::<OwnedFd, u16>();
        Ok((opt_fd.unwrap(), opt_mtu.unwrap()))
    }

    pub fn acquire_write(&self) -> Result<(OwnedFd, u16), Box<Error>> {
        let mut m = Message::new_method_call(
            SERVICE_NAME,
            &self.object_path,
            GATT_CHARACTERISTIC_INTERFACE,
            "AcquireWrite",
        )?;
        m.append_items(&[MessageItem::Array(
            MessageItemArray::new(vec![], Signature::from("a{sv}")).unwrap(),
        )]);
        let reply = self
            .session
            .get_connection()
            .send_with_reply_and_block(m, 1000)?;
        let (opt_fd, opt_mtu) = reply.get2::<OwnedFd, u16>();
        Ok((opt_fd.unwrap(), opt_mtu.unwrap()))
    }
}
<|endoftext|>


<commit_before>//! The dump module contains code related for outputing/dumping data.
use std::fmt::Display;
use std::convert::From;
use super::errors::*;

/// Enum which provides all possible output value formats supported by the dump module.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum OutputFormat {
    Hex,
    Decimal,
    Octal,
    Binary,
}

impl From<String> for OutputFormat {
    fn from(format_string: String) -> Self {
        match format_string.to_lowercase().as_ref() {
            "hex" => OutputFormat::Hex,
            "dec" => OutputFormat::Decimal,
            "oct" => OutputFormat::Octal,
            "bin" => OutputFormat::Binary,
            _ => panic!("Invalid output format"),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct OutputSettings {
    start_address: usize,
    show_address: bool,
    group_size: usize,
    columns: usize,
    show_interpretation: bool,
    output_fmt: OutputFormat,
}

impl OutputSettings {
    pub fn new() -> OutputSettings {
        OutputSettings {
            start_address: 0,
            show_address: true,
            group_size: 1,
            columns: 8,
            show_interpretation: true,
            output_fmt: OutputFormat::Hex,
        }
    }

    pub fn bytes_per_line(&self) -> usize {
        self.columns * self.group_size
    }

    pub fn start_address(mut self, address: usize) -> Self {
        self.start_address = address;
        self
    }

    pub fn show_address(mut self, show: bool) -> Self {
        self.show_address = show;
        self
    }

    pub fn group_size(mut self, size: usize) -> Self {
        self.group_size = size;
        self
    }

    pub fn columns(mut self, columns: usize) -> Self {
        self.columns = columns;
        self
    }

    pub fn show_interpretation(mut self, show: bool) -> Self {
        self.show_interpretation = show;
        self
    }

    pub fn format(mut self, fmt: OutputFormat) -> Self {
        self.output_fmt = fmt;
        self
    }
}

/// The `OutputLine` struct contains all  information needed to dump/output a single line of data.
#[derive(Debug)]
pub struct OutputLine<'a> {
    output_settings: OutputSettings,
    data: &'a [u8],
}

impl<'a> OutputLine<'a> {
    pub fn new(data: &[u8]) -> OutputLine {
        OutputLine {
            output_settings: OutputSettings::new(),
            data: data,
        }
    }

    pub fn format(self, settings: OutputSettings) -> Self {
        OutputLine {
            output_settings: settings,
            data: self.data,
        }
    }

    fn write_address(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        write!(f, "{:08.X}: ", self.output_settings.start_address)?;
        Ok(10)
    }

    fn write_bytes(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        let mut byte_count = 0;
        let mut bytes_written = 0;
        for b in self.data.iter() {
            byte_count += 1;
            let is_seperator_necessary = byte_count % self.output_settings.group_size == 0;
            if is_seperator_necessary {
                bytes_written += self.write_formated_byte(f, b)?;
                write!(f, " ")?;
                bytes_written += 1;
            } else {
                bytes_written += self.write_formated_byte(f, b)?;
            }
        }
        Ok(bytes_written)
    }

    fn write_formated_byte(&self, f: &mut ::fmt::Formatter, byte: &u8) -> Result<usize> {
        match self.output_settings.output_fmt {
            OutputFormat::Hex => {
                write!(f, "{:02.X}", byte)?;
                Ok(2)
            }
            OutputFormat::Octal => {
                write!(f, "{:03.o}", byte)?;
                Ok(3)
            }
            OutputFormat::Decimal => {
                write!(f, "{:03}", byte)?;
                Ok(3)
            }
            OutputFormat::Binary => {
                write!(f, "{:08b}", byte)?;
                Ok(8)
            }
        }
    }

    fn write_interpretation(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        write!(f, "   ");
        for b in self.data.iter() {
            match *b {
                character @ 20u8...126u8 => write!(f, "{}", character as char)?,
                _ => write!(f, "{}", ".")?,
            }
        }
        Ok(self.data.len())
    }
}

impl<'a> ::fmt::Display for OutputLine<'a> {
    fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
        let format_size = |fmt: OutputFormat| match fmt {
            OutputFormat::Hex => 2,
            OutputFormat::Octal => 3,
            OutputFormat::Decimal => 3,
            OutputFormat::Binary => 8,
        };
        if self.output_settings.show_address {
            self.write_address(f);
        }
        // FIXME NiCo: whats the issues here?!
        let bytes_written = self.write_bytes(f).unwrap();
        let expected_length = self.output_settings.columns * self.output_settings.group_size *
                              format_size(self.output_settings.output_fmt) +
                              (self.output_settings.columns);
        let padding = expected_length - bytes_written;
        for i in 0..padding {
            write!(f, " ")?;
        }
        if self.output_settings.show_interpretation {
            self.write_interpretation(f);
        }
        Ok(())
    }
}

mod test {

    use super::*;
    use std::fmt::Write;

    struct TestFixture {
        data: [u8; 8],
        small_data: [u8; 5],
    }

    impl TestFixture {
        fn new() -> Self {
            TestFixture {
                data: [0, 255, 127, 128, 56, 65, 1, 33],
                small_data: [0, 255, 80, 44, 7],
            }
        }

        fn data(&self) -> &[u8] {
            &self.data
        }

        fn small_data(&self) -> &[u8] {
            &self.small_data
        }
    }

    #[test]
    fn output_settings_can_be_constructed() {
        let output_settings = OutputSettings::new();
        assert!(true);
    }

    #[test]
    fn output_settings_builder() {
        let format = OutputFormat::Binary;
        let start_address = 0xFF00AABB;
        let group_size = 2;
        let show_address = false;
        let show_interpretation = false;

        let mut output_settings = OutputSettings::new()
            .format(format)
            .start_address(start_address)
            .group_size(group_size)
            .show_address(show_address)
            .show_interpretation(show_interpretation);

        assert_eq!(output_settings.output_fmt, format);
        assert_eq!(output_settings.start_address, start_address);
        assert_eq!(output_settings.group_size, group_size);
        assert_eq!(output_settings.show_address, show_address);
        assert_eq!(output_settings.show_interpretation, show_interpretation);
    }

    #[test]
    fn output_settings_get_bytes_per_line() {
        {
            let group_size = 8;
            let columns = 2;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
        {
            let group_size = 5;
            let columns = 4;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
        {
            let group_size = 9;
            let columns = 4;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
    }

    #[test]
    fn output_settings_from_string() {
        assert_eq!(OutputFormat::Binary, OutputFormat::from("bin".to_string()));
        assert_eq!(OutputFormat::Hex, OutputFormat::from("hex".to_string()));
        assert_eq!(OutputFormat::Octal, OutputFormat::from("oct".to_string()));
        assert_eq!(OutputFormat::Decimal, OutputFormat::from("dec".to_string()));

        assert_eq!(OutputFormat::Binary, OutputFormat::from("Bin".to_string()));
        assert_eq!(OutputFormat::Hex, OutputFormat::from("hEx".to_string()));
        assert_eq!(OutputFormat::Octal, OutputFormat::from("ocT".to_string()));
        assert_eq!(OutputFormat::Decimal, OutputFormat::from("DEC".to_string()));
    }

    #[test]
    fn outputline_can_be_constructed() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.data());
        assert!(true);
    }

    #[test]
    fn default_output_format_for_a_single_line() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.data());
        let expected_output = "00000000: 00 FF 7F 80 38 41 01 21    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn default_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.small_data());
        let expected_output = "00000000: 00 FF 50 2C 07             ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn octal_output_format_on_a_single_line() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Octal);
        let output_line = OutputLine::new(fixture.data()).format(output_settings);
        let expected_output = "00000000: 000 377 177 200 070 101 001 041    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn octal_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Octal);
        let output_line = OutputLine::new(fixture.small_data()).format(output_settings);
        let expected_output = "00000000: 000 377 120 054 007                ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn binary_output_format_on_a_single_line() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Binary);
        let output_line = OutputLine::new(fixture.data()).format(output_settings);
        let expected_output = "00000000: 00000000 11111111 01111111 10000000 00111000 01000001 00000001 00100001    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn binary_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Binary);
        let output_line = OutputLine::new(fixture.small_data()).format(output_settings);
        let expected_output = "00000000: 00000000 11111111 01010000 00101100 00000111                               ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }
}
<commit_msg>Remove unwrap call(s)<commit_after>//! The dump module contains code related for outputing/dumping data.
use std::fmt::Display;
use std::fmt::Error;
use std::convert::From;
use super::errors::*;
use std::convert::Into;

/// Enum which provides all possible output value formats supported by the dump module.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum OutputFormat {
    Hex,
    Decimal,
    Octal,
    Binary,
}

impl From<String> for OutputFormat {
    fn from(format_string: String) -> Self {
        match format_string.to_lowercase().as_ref() {
            "hex" => OutputFormat::Hex,
            "dec" => OutputFormat::Decimal,
            "oct" => OutputFormat::Octal,
            "bin" => OutputFormat::Binary,
            _ => panic!("Invalid output format"),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct OutputSettings {
    start_address: usize,
    show_address: bool,
    group_size: usize,
    columns: usize,
    show_interpretation: bool,
    output_fmt: OutputFormat,
}

impl OutputSettings {
    pub fn new() -> OutputSettings {
        OutputSettings {
            start_address: 0,
            show_address: true,
            group_size: 1,
            columns: 8,
            show_interpretation: true,
            output_fmt: OutputFormat::Hex,
        }
    }

    pub fn bytes_per_line(&self) -> usize {
        self.columns * self.group_size
    }

    pub fn start_address(mut self, address: usize) -> Self {
        self.start_address = address;
        self
    }

    pub fn show_address(mut self, show: bool) -> Self {
        self.show_address = show;
        self
    }

    pub fn group_size(mut self, size: usize) -> Self {
        self.group_size = size;
        self
    }

    pub fn columns(mut self, columns: usize) -> Self {
        self.columns = columns;
        self
    }

    pub fn show_interpretation(mut self, show: bool) -> Self {
        self.show_interpretation = show;
        self
    }

    pub fn format(mut self, fmt: OutputFormat) -> Self {
        self.output_fmt = fmt;
        self
    }
}

/// The `OutputLine` struct contains all  information needed to dump/output a single line of data.
#[derive(Debug)]
pub struct OutputLine<'a> {
    output_settings: OutputSettings,
    data: &'a [u8],
}

impl<'a> OutputLine<'a> {
    pub fn new(data: &[u8]) -> OutputLine {
        OutputLine {
            output_settings: OutputSettings::new(),
            data: data,
        }
    }

    pub fn format(self, settings: OutputSettings) -> Self {
        OutputLine {
            output_settings: settings,
            data: self.data,
        }
    }

    fn write_address(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        write!(f, "{:08.X}: ", self.output_settings.start_address)?;
        Ok(10)
    }

    fn write_bytes(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        let mut byte_count = 0;
        let mut bytes_written = 0;
        for b in self.data.iter() {
            byte_count += 1;
            let is_seperator_necessary = byte_count % self.output_settings.group_size == 0;
            if is_seperator_necessary {
                bytes_written += self.write_formated_byte(f, b)?;
                write!(f, " ")?;
                bytes_written += 1;
            } else {
                bytes_written += self.write_formated_byte(f, b)?;
            }
        }
        Ok(bytes_written)
    }

    fn write_formated_byte(&self, f: &mut ::fmt::Formatter, byte: &u8) -> Result<usize> {
        match self.output_settings.output_fmt {
            OutputFormat::Hex => {
                write!(f, "{:02.X}", byte)?;
                Ok(2)
            }
            OutputFormat::Octal => {
                write!(f, "{:03.o}", byte)?;
                Ok(3)
            }
            OutputFormat::Decimal => {
                write!(f, "{:03}", byte)?;
                Ok(3)
            }
            OutputFormat::Binary => {
                write!(f, "{:08b}", byte)?;
                Ok(8)
            }
        }
    }

    fn write_interpretation(&self, f: &mut ::fmt::Formatter) -> Result<usize> {
        write!(f, "   ");
        for b in self.data.iter() {
            match *b {
                character @ 20u8...126u8 => write!(f, "{}", character as char)?,
                _ => write!(f, "{}", ".")?,
            }
        }
        Ok(self.data.len())
    }
}

impl<'a> ::fmt::Display for OutputLine<'a> {
    fn fmt(&self, f: &mut ::fmt::Formatter) -> ::std::fmt::Result {
        let format_size = |fmt: OutputFormat| match fmt {
            OutputFormat::Hex => 2,
            OutputFormat::Octal => 3,
            OutputFormat::Decimal => 3,
            OutputFormat::Binary => 8,
        };
        if self.output_settings.show_address {
            self.write_address(f);
        }
        let bytes_written = self.write_bytes(f).map_err(|e| ::std::fmt::Error)?;
        let expected_length = self.output_settings.columns * self.output_settings.group_size *
                              format_size(self.output_settings.output_fmt) +
                              (self.output_settings.columns);
        let padding = expected_length - bytes_written;
        for i in 0..padding {
            write!(f, " ")?;
        }
        if self.output_settings.show_interpretation {
            self.write_interpretation(f);
        }
        Ok(())
    }
}

mod test {

    use super::*;
    use std::fmt::Write;

    struct TestFixture {
        data: [u8; 8],
        small_data: [u8; 5],
    }

    impl TestFixture {
        fn new() -> Self {
            TestFixture {
                data: [0, 255, 127, 128, 56, 65, 1, 33],
                small_data: [0, 255, 80, 44, 7],
            }
        }

        fn data(&self) -> &[u8] {
            &self.data
        }

        fn small_data(&self) -> &[u8] {
            &self.small_data
        }
    }

    #[test]
    fn output_settings_can_be_constructed() {
        let output_settings = OutputSettings::new();
        assert!(true);
    }

    #[test]
    fn output_settings_builder() {
        let format = OutputFormat::Binary;
        let start_address = 0xFF00AABB;
        let group_size = 2;
        let show_address = false;
        let show_interpretation = false;

        let mut output_settings = OutputSettings::new()
            .format(format)
            .start_address(start_address)
            .group_size(group_size)
            .show_address(show_address)
            .show_interpretation(show_interpretation);

        assert_eq!(output_settings.output_fmt, format);
        assert_eq!(output_settings.start_address, start_address);
        assert_eq!(output_settings.group_size, group_size);
        assert_eq!(output_settings.show_address, show_address);
        assert_eq!(output_settings.show_interpretation, show_interpretation);
    }

    #[test]
    fn output_settings_get_bytes_per_line() {
        {
            let group_size = 8;
            let columns = 2;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
        {
            let group_size = 5;
            let columns = 4;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
        {
            let group_size = 9;
            let columns = 4;
            let mut output_settings = OutputSettings::new().columns(columns).group_size(group_size);
            assert_eq!(group_size * columns, output_settings.bytes_per_line())
        }
    }

    #[test]
    fn output_settings_from_string() {
        assert_eq!(OutputFormat::Binary, OutputFormat::from("bin".to_string()));
        assert_eq!(OutputFormat::Hex, OutputFormat::from("hex".to_string()));
        assert_eq!(OutputFormat::Octal, OutputFormat::from("oct".to_string()));
        assert_eq!(OutputFormat::Decimal, OutputFormat::from("dec".to_string()));

        assert_eq!(OutputFormat::Binary, OutputFormat::from("Bin".to_string()));
        assert_eq!(OutputFormat::Hex, OutputFormat::from("hEx".to_string()));
        assert_eq!(OutputFormat::Octal, OutputFormat::from("ocT".to_string()));
        assert_eq!(OutputFormat::Decimal, OutputFormat::from("DEC".to_string()));
    }

    #[test]
    fn outputline_can_be_constructed() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.data());
        assert!(true);
    }

    #[test]
    fn default_output_format_for_a_single_line() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.data());
        let expected_output = "00000000: 00 FF 7F 80 38 41 01 21    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn default_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_line = OutputLine::new(fixture.small_data());
        let expected_output = "00000000: 00 FF 50 2C 07             ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn octal_output_format_on_a_single_line() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Octal);
        let output_line = OutputLine::new(fixture.data()).format(output_settings);
        let expected_output = "00000000: 000 377 177 200 070 101 001 041    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn octal_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Octal);
        let output_line = OutputLine::new(fixture.small_data()).format(output_settings);
        let expected_output = "00000000: 000 377 120 054 007                ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn binary_output_format_on_a_single_line() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Binary);
        let output_line = OutputLine::new(fixture.data()).format(output_settings);
        let expected_output = "00000000: 00000000 11111111 01111111 10000000 00111000 01000001 00000001 00100001    ....8A.!";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }

    #[test]
    fn binary_output_format_for_a_single_line_with_padding() {
        let fixture = TestFixture::new();
        let output_settings = OutputSettings::new().format(OutputFormat::Binary);
        let output_line = OutputLine::new(fixture.small_data()).format(output_settings);
        let expected_output = "00000000: 00000000 11111111 01010000 00101100 00000111                               ..P,.";
        let mut buffer = String::new();
        let result = write!(&mut buffer, "{}", output_line);
        assert_eq!(Ok(()), result);
        assert_eq!(expected_output, buffer);
    }
}
<|endoftext|>


<commit_before>use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData};
use crate::core::profiles::Profiles;
use crate::core::{PackageIdSpec, TargetKind, Workspace};
use crate::ops;
use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::lev_distance;
use crate::util::Config;

use anyhow::Context as _;
use cargo_util::paths;
use std::fs;
use std::path::Path;

pub struct CleanOptions<'a> {
    pub config: &'a Config,
    /// A list of packages to clean. If empty, everything is cleaned.
    pub spec: Vec<String>,
    /// The target arch triple to clean, or None for the host arch
    pub targets: Vec<String>,
    /// Whether to clean the release directory
    pub profile_specified: bool,
    /// Whether to clean the directory of a certain build profile
    pub requested_profile: InternedString,
    /// Whether to just clean the doc directory
    pub doc: bool,
}

/// Cleans the package's build artifacts.
pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
    let mut target_dir = ws.target_dir();
    let config = ws.config();

    // If the doc option is set, we just want to delete the doc directory.
    if opts.doc {
        target_dir = target_dir.join("doc");
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    let profiles = Profiles::new(ws, opts.requested_profile)?;

    if opts.profile_specified {
        // After parsing profiles we know the dir-name of the profile, if a profile
        // was passed from the command line. If so, delete only the directory of
        // that profile.
        let dir_name = profiles.get_dir_name();
        target_dir = target_dir.join(dir_name);
    }

    // If we have a spec, then we need to delete some packages, otherwise, just
    // remove the whole target directory and be done with it!
    //
    // Note that we don't bother grabbing a lock here as we're just going to
    // blow it all away anyway.
    if opts.spec.is_empty() {
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    // Clean specific packages.
    let requested_kinds = CompileKind::from_requested_targets(config, &opts.targets)?;
    let target_data = RustcTargetData::new(ws, &requested_kinds)?;
    let (pkg_set, resolve) = ops::resolve_ws(ws)?;
    let prof_dir_name = profiles.get_dir_name();
    let host_layout = Layout::new(ws, None, &prof_dir_name)?;
    // Convert requested kinds to a Vec of layouts.
    let target_layouts: Vec<(CompileKind, Layout)> = requested_kinds
        .into_iter()
        .filter_map(|kind| match kind {
            CompileKind::Target(target) => match Layout::new(ws, Some(target), &prof_dir_name) {
                Ok(layout) => Some(Ok((kind, layout))),
                Err(e) => Some(Err(e)),
            },
            CompileKind::Host => None,
        })
        .collect::<CargoResult<_>>()?;
    // A Vec of layouts. This is a little convoluted because there can only be
    // one host_layout.
    let layouts = if opts.targets.is_empty() {
        vec![(CompileKind::Host, &host_layout)]
    } else {
        target_layouts
            .iter()
            .map(|(kind, layout)| (*kind, layout))
            .collect()
    };
    // Create a Vec that also includes the host for things that need to clean both.
    let layouts_with_host: Vec<(CompileKind, &Layout)> =
        std::iter::once((CompileKind::Host, &host_layout))
            .chain(layouts.iter().map(|(k, l)| (*k, *l)))
            .collect();

    // Cleaning individual rustdoc crates is currently not supported.
    // For example, the search index would need to be rebuilt to fully
    // remove it (otherwise you're left with lots of broken links).
    // Doc tests produce no output.

    // Get Packages for the specified specs.
    let mut pkg_ids = Vec::new();
    for spec_str in opts.spec.iter() {
        // Translate the spec to a Package.
        let spec = PackageIdSpec::parse(spec_str)?;
        if spec.version().is_some() {
            config.shell().warn(&format!(
                "version qualifier in `-p {}` is ignored, \
                cleaning all versions of `{}` found",
                spec_str,
                spec.name()
            ))?;
        }
        if spec.url().is_some() {
            config.shell().warn(&format!(
                "url qualifier in `-p {}` ignored, \
                cleaning all versions of `{}` found",
                spec_str,
                spec.name()
            ))?;
        }
        let matches: Vec<_> = resolve.iter().filter(|id| spec.matches(*id)).collect();
        if matches.is_empty() {
            let mut suggestion = String::new();
            suggestion.push_str(&lev_distance::closest_msg(
                &spec.name(),
                resolve.iter(),
                |id| id.name().as_str(),
            ));
            anyhow::bail!(
                "package ID specification `{}` did not match any packages{}",
                spec,
                suggestion
            );
        }
        pkg_ids.extend(matches);
    }
    let packages = pkg_set.get_many(pkg_ids)?;

    for pkg in packages {
        let pkg_dir = format!("{}-*", pkg.name());

        // Clean fingerprints.
        for (_, layout) in &layouts_with_host {
            let dir = escape_glob_path(layout.fingerprint())?;
            rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
        }

        for target in pkg.targets() {
            if target.is_custom_build() {
                // Get both the build_script_build and the output directory.
                for (_, layout) in &layouts_with_host {
                    let dir = escape_glob_path(layout.build())?;
                    rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config)?;
                }
                continue;
            }
            let crate_name = target.crate_name();
            for &mode in &[
                CompileMode::Build,
                CompileMode::Test,
                CompileMode::Check { test: false },
            ] {
                for (compile_kind, layout) in &layouts {
                    let triple = target_data.short_name(compile_kind);

                    let (file_types, _unsupported) = target_data
                        .info(*compile_kind)
                        .rustc_outputs(mode, target.kind(), triple)?;
                    let (dir, uplift_dir) = match target.kind() {
                        TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
                            (layout.examples(), Some(layout.examples()))
                        }
                        // Tests/benchmarks are never uplifted.
                        TargetKind::Test | TargetKind::Bench => (layout.deps(), None),
                        _ => (layout.deps(), Some(layout.dest())),
                    };
                    for file_type in file_types {
                        // Some files include a hash in the filename, some don't.
                        let hashed_name = file_type.output_filename(target, Some("*"));
                        let unhashed_name = file_type.output_filename(target, None);
                        let dir_glob = escape_glob_path(dir)?;
                        let dir_glob = Path::new(&dir_glob);

                        rm_rf_glob(&dir_glob.join(&hashed_name), config)?;
                        rm_rf(&dir.join(&unhashed_name), config)?;
                        // Remove dep-info file generated by rustc. It is not tracked in
                        // file_types. It does not have a prefix.
                        let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name));
                        rm_rf_glob(&hashed_dep_info, config)?;
                        let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
                        rm_rf(&unhashed_dep_info, config)?;
                        // Remove split-debuginfo files generated by rustc.
                        let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name));
                        rm_rf_glob(&split_debuginfo_obj, config)?;
                        let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name));
                        rm_rf_glob(&split_debuginfo_dwo, config)?;

                        // Remove the uplifted copy.
                        if let Some(uplift_dir) = uplift_dir {
                            let uplifted_path = uplift_dir.join(file_type.uplift_filename(target));
                            rm_rf(&uplifted_path, config)?;
                            // Dep-info generated by Cargo itself.
                            let dep_info = uplifted_path.with_extension("d");
                            rm_rf(&dep_info, config)?;
                        }
                    }
                    // TODO: what to do about build_script_build?
                    let dir = escape_glob_path(layout.incremental())?;
                    let incremental = Path::new(&dir).join(format!("{}-*", crate_name));
                    rm_rf_glob(&incremental, config)?;
                }
            }
        }
    }

    Ok(())
}

fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
    let pattern = pattern
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
    Ok(glob::Pattern::escape(pattern))
}

fn rm_rf_glob(pattern: &Path, config: &Config) -> CargoResult<()> {
    // TODO: Display utf8 warning to user?  Or switch to globset?
    let pattern = pattern
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
    for path in glob::glob(pattern)? {
        rm_rf(&path?, config)?;
    }
    Ok(())
}

fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
    let m = fs::symlink_metadata(path);
    if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_dir_all(path).with_context(|| "could not remove build directory")?;
    } else if m.is_ok() {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_file(path).with_context(|| "failed to remove build artifact")?;
    }
    Ok(())
}
<commit_msg>Auto merge of #10236 - theo-lw:master, r=weihanglo<commit_after>use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData};
use crate::core::profiles::Profiles;
use crate::core::{PackageIdSpec, TargetKind, Workspace};
use crate::ops;
use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::lev_distance;
use crate::util::{Config, Progress, ProgressStyle};

use anyhow::Context as _;
use cargo_util::paths;
use std::fs;
use std::path::Path;

pub struct CleanOptions<'a> {
    pub config: &'a Config,
    /// A list of packages to clean. If empty, everything is cleaned.
    pub spec: Vec<String>,
    /// The target arch triple to clean, or None for the host arch
    pub targets: Vec<String>,
    /// Whether to clean the release directory
    pub profile_specified: bool,
    /// Whether to clean the directory of a certain build profile
    pub requested_profile: InternedString,
    /// Whether to just clean the doc directory
    pub doc: bool,
}

/// Cleans the package's build artifacts.
pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
    let mut target_dir = ws.target_dir();
    let config = ws.config();

    // If the doc option is set, we just want to delete the doc directory.
    if opts.doc {
        target_dir = target_dir.join("doc");
        return clean_entire_folder(&target_dir.into_path_unlocked(), config);
    }

    let profiles = Profiles::new(ws, opts.requested_profile)?;

    if opts.profile_specified {
        // After parsing profiles we know the dir-name of the profile, if a profile
        // was passed from the command line. If so, delete only the directory of
        // that profile.
        let dir_name = profiles.get_dir_name();
        target_dir = target_dir.join(dir_name);
    }

    // If we have a spec, then we need to delete some packages, otherwise, just
    // remove the whole target directory and be done with it!
    //
    // Note that we don't bother grabbing a lock here as we're just going to
    // blow it all away anyway.
    if opts.spec.is_empty() {
        return clean_entire_folder(&target_dir.into_path_unlocked(), config);
    }

    // Clean specific packages.
    let requested_kinds = CompileKind::from_requested_targets(config, &opts.targets)?;
    let target_data = RustcTargetData::new(ws, &requested_kinds)?;
    let (pkg_set, resolve) = ops::resolve_ws(ws)?;
    let prof_dir_name = profiles.get_dir_name();
    let host_layout = Layout::new(ws, None, &prof_dir_name)?;
    // Convert requested kinds to a Vec of layouts.
    let target_layouts: Vec<(CompileKind, Layout)> = requested_kinds
        .into_iter()
        .filter_map(|kind| match kind {
            CompileKind::Target(target) => match Layout::new(ws, Some(target), &prof_dir_name) {
                Ok(layout) => Some(Ok((kind, layout))),
                Err(e) => Some(Err(e)),
            },
            CompileKind::Host => None,
        })
        .collect::<CargoResult<_>>()?;
    // A Vec of layouts. This is a little convoluted because there can only be
    // one host_layout.
    let layouts = if opts.targets.is_empty() {
        vec![(CompileKind::Host, &host_layout)]
    } else {
        target_layouts
            .iter()
            .map(|(kind, layout)| (*kind, layout))
            .collect()
    };
    // Create a Vec that also includes the host for things that need to clean both.
    let layouts_with_host: Vec<(CompileKind, &Layout)> =
        std::iter::once((CompileKind::Host, &host_layout))
            .chain(layouts.iter().map(|(k, l)| (*k, *l)))
            .collect();

    // Cleaning individual rustdoc crates is currently not supported.
    // For example, the search index would need to be rebuilt to fully
    // remove it (otherwise you're left with lots of broken links).
    // Doc tests produce no output.

    // Get Packages for the specified specs.
    let mut pkg_ids = Vec::new();
    for spec_str in opts.spec.iter() {
        // Translate the spec to a Package.
        let spec = PackageIdSpec::parse(spec_str)?;
        if spec.version().is_some() {
            config.shell().warn(&format!(
                "version qualifier in `-p {}` is ignored, \
                cleaning all versions of `{}` found",
                spec_str,
                spec.name()
            ))?;
        }
        if spec.url().is_some() {
            config.shell().warn(&format!(
                "url qualifier in `-p {}` ignored, \
                cleaning all versions of `{}` found",
                spec_str,
                spec.name()
            ))?;
        }
        let matches: Vec<_> = resolve.iter().filter(|id| spec.matches(*id)).collect();
        if matches.is_empty() {
            let mut suggestion = String::new();
            suggestion.push_str(&lev_distance::closest_msg(
                &spec.name(),
                resolve.iter(),
                |id| id.name().as_str(),
            ));
            anyhow::bail!(
                "package ID specification `{}` did not match any packages{}",
                spec,
                suggestion
            );
        }
        pkg_ids.extend(matches);
    }
    let packages = pkg_set.get_many(pkg_ids)?;

    let mut progress = CleaningPackagesBar::new(config, packages.len());
    for pkg in packages {
        let pkg_dir = format!("{}-*", pkg.name());
        progress.on_cleaning_package(&pkg.name())?;

        // Clean fingerprints.
        for (_, layout) in &layouts_with_host {
            let dir = escape_glob_path(layout.fingerprint())?;
            rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
        }

        for target in pkg.targets() {
            if target.is_custom_build() {
                // Get both the build_script_build and the output directory.
                for (_, layout) in &layouts_with_host {
                    let dir = escape_glob_path(layout.build())?;
                    rm_rf_glob(&Path::new(&dir).join(&pkg_dir), config, &mut progress)?;
                }
                continue;
            }
            let crate_name = target.crate_name();
            for &mode in &[
                CompileMode::Build,
                CompileMode::Test,
                CompileMode::Check { test: false },
            ] {
                for (compile_kind, layout) in &layouts {
                    let triple = target_data.short_name(compile_kind);

                    let (file_types, _unsupported) = target_data
                        .info(*compile_kind)
                        .rustc_outputs(mode, target.kind(), triple)?;
                    let (dir, uplift_dir) = match target.kind() {
                        TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
                            (layout.examples(), Some(layout.examples()))
                        }
                        // Tests/benchmarks are never uplifted.
                        TargetKind::Test | TargetKind::Bench => (layout.deps(), None),
                        _ => (layout.deps(), Some(layout.dest())),
                    };
                    for file_type in file_types {
                        // Some files include a hash in the filename, some don't.
                        let hashed_name = file_type.output_filename(target, Some("*"));
                        let unhashed_name = file_type.output_filename(target, None);
                        let dir_glob = escape_glob_path(dir)?;
                        let dir_glob = Path::new(&dir_glob);

                        rm_rf_glob(&dir_glob.join(&hashed_name), config, &mut progress)?;
                        rm_rf(&dir.join(&unhashed_name), config, &mut progress)?;
                        // Remove dep-info file generated by rustc. It is not tracked in
                        // file_types. It does not have a prefix.
                        let hashed_dep_info = dir_glob.join(format!("{}-*.d", crate_name));
                        rm_rf_glob(&hashed_dep_info, config, &mut progress)?;
                        let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
                        rm_rf(&unhashed_dep_info, config, &mut progress)?;
                        // Remove split-debuginfo files generated by rustc.
                        let split_debuginfo_obj = dir_glob.join(format!("{}.*.o", crate_name));
                        rm_rf_glob(&split_debuginfo_obj, config, &mut progress)?;
                        let split_debuginfo_dwo = dir_glob.join(format!("{}.*.dwo", crate_name));
                        rm_rf_glob(&split_debuginfo_dwo, config, &mut progress)?;

                        // Remove the uplifted copy.
                        if let Some(uplift_dir) = uplift_dir {
                            let uplifted_path = uplift_dir.join(file_type.uplift_filename(target));
                            rm_rf(&uplifted_path, config, &mut progress)?;
                            // Dep-info generated by Cargo itself.
                            let dep_info = uplifted_path.with_extension("d");
                            rm_rf(&dep_info, config, &mut progress)?;
                        }
                    }
                    // TODO: what to do about build_script_build?
                    let dir = escape_glob_path(layout.incremental())?;
                    let incremental = Path::new(&dir).join(format!("{}-*", crate_name));
                    rm_rf_glob(&incremental, config, &mut progress)?;
                }
            }
        }
    }

    Ok(())
}

fn escape_glob_path(pattern: &Path) -> CargoResult<String> {
    let pattern = pattern
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
    Ok(glob::Pattern::escape(pattern))
}

fn rm_rf_glob(
    pattern: &Path,
    config: &Config,
    progress: &mut dyn CleaningProgressBar,
) -> CargoResult<()> {
    // TODO: Display utf8 warning to user?  Or switch to globset?
    let pattern = pattern
        .to_str()
        .ok_or_else(|| anyhow::anyhow!("expected utf-8 path"))?;
    for path in glob::glob(pattern)? {
        rm_rf(&path?, config, progress)?;
    }
    Ok(())
}

fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> {
    if fs::symlink_metadata(path).is_err() {
        return Ok(());
    }

    config
        .shell()
        .verbose(|shell| shell.status("Removing", path.display()))?;
    progress.display_now()?;

    for entry in walkdir::WalkDir::new(path).contents_first(true) {
        let entry = entry?;
        progress.on_clean()?;
        if entry.file_type().is_dir() {
            paths::remove_dir(entry.path()).with_context(|| "could not remove build directory")?;
        } else {
            paths::remove_file(entry.path()).with_context(|| "failed to remove build artifact")?;
        }
    }

    Ok(())
}

fn clean_entire_folder(path: &Path, config: &Config) -> CargoResult<()> {
    let num_paths = walkdir::WalkDir::new(path).into_iter().count();
    let mut progress = CleaningFolderBar::new(config, num_paths);
    rm_rf(path, config, &mut progress)
}

trait CleaningProgressBar {
    fn display_now(&mut self) -> CargoResult<()>;
    fn on_clean(&mut self) -> CargoResult<()>;
}

struct CleaningFolderBar<'cfg> {
    bar: Progress<'cfg>,
    max: usize,
    cur: usize,
}

impl<'cfg> CleaningFolderBar<'cfg> {
    fn new(cfg: &'cfg Config, max: usize) -> Self {
        Self {
            bar: Progress::with_style("Cleaning", ProgressStyle::Percentage, cfg),
            max,
            cur: 0,
        }
    }

    fn cur_progress(&self) -> usize {
        std::cmp::min(self.cur, self.max)
    }
}

impl<'cfg> CleaningProgressBar for CleaningFolderBar<'cfg> {
    fn display_now(&mut self) -> CargoResult<()> {
        self.bar.tick_now(self.cur_progress(), self.max, "")
    }

    fn on_clean(&mut self) -> CargoResult<()> {
        self.cur += 1;
        self.bar.tick(self.cur_progress(), self.max, "")
    }
}

struct CleaningPackagesBar<'cfg> {
    bar: Progress<'cfg>,
    max: usize,
    cur: usize,
    num_files_folders_cleaned: usize,
    package_being_cleaned: String,
}

impl<'cfg> CleaningPackagesBar<'cfg> {
    fn new(cfg: &'cfg Config, max: usize) -> Self {
        Self {
            bar: Progress::with_style("Cleaning", ProgressStyle::Ratio, cfg),
            max,
            cur: 0,
            num_files_folders_cleaned: 0,
            package_being_cleaned: String::new(),
        }
    }

    fn on_cleaning_package(&mut self, package: &str) -> CargoResult<()> {
        self.cur += 1;
        self.package_being_cleaned = String::from(package);
        self.bar
            .tick(self.cur_progress(), self.max, &self.format_message())
    }

    fn cur_progress(&self) -> usize {
        std::cmp::min(self.cur, self.max)
    }

    fn format_message(&self) -> String {
        format!(
            ": {}, {} files/folders cleaned",
            self.package_being_cleaned, self.num_files_folders_cleaned
        )
    }
}

impl<'cfg> CleaningProgressBar for CleaningPackagesBar<'cfg> {
    fn display_now(&mut self) -> CargoResult<()> {
        self.bar
            .tick_now(self.cur_progress(), self.max, &self.format_message())
    }

    fn on_clean(&mut self) -> CargoResult<()> {
        self.bar
            .tick(self.cur_progress(), self.max, &self.format_message())?;
        self.num_files_folders_cleaned += 1;
        Ok(())
    }
}
<|endoftext|>