<commit_before>// Copyright 2018 The xi-editor Authors.
//
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Interactions with the file system.

use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str;
use std::time::SystemTime;

use xi_rope::Rope;
use xi_rpc::RemoteError;

use tabs::BufferId;

#[cfg(feature = "notify")]
use tabs::OPEN_FILE_EVENT_TOKEN;
#[cfg(feature = "notify")]
use watcher::FileWatcher;

const UTF8_BOM: &str = "\u{feff}";

/// Tracks all state related to open files.
pub struct FileManager {
    open_files: HashMap<PathBuf, BufferId>,
    file_info: HashMap<BufferId, FileInfo>,
    /// A monitor of filesystem events, for things like reloading changed files.
    #[cfg(feature = "notify")]
    watcher: FileWatcher,
}

#[derive(Debug)]
pub struct FileInfo {
    pub encoding: CharacterEncoding,
    pub path: PathBuf,
    pub mod_time: Option<SystemTime>,
    pub has_changed: bool,
}

pub enum FileError {
    Io(io::Error, PathBuf),
    UnknownEncoding(PathBuf),
    HasChanged(PathBuf),
}

#[derive(Debug, Clone, Copy)]
pub enum CharacterEncoding {
    Utf8,
    Utf8WithBom,
}

impl FileManager {
    #[cfg(feature = "notify")]
    pub fn new(watcher: FileWatcher) -> Self {
        FileManager { open_files: HashMap::new(), file_info: HashMap::new(), watcher }
    }

    #[cfg(not(feature = "notify"))]
    pub fn new() -> Self {
        FileManager { open_files: HashMap::new(), file_info: HashMap::new() }
    }

    #[cfg(feature = "notify")]
    pub fn watcher(&mut self) -> &mut FileWatcher {
        &mut self.watcher
    }

    pub fn get_info(&self, id: BufferId) -> Option<&FileInfo> {
        self.file_info.get(&id)
    }

    pub fn get_editor(&self, path: &Path) -> Option<BufferId> {
        self.open_files.get(path).cloned()
    }

    /// Returns `true` if this file is open and has changed on disk.
    /// This state is stashed.
    pub fn check_file(&mut self, path: &Path, id: BufferId) -> bool {
        if let Some(info) = self.file_info.get_mut(&id) {
            let mod_t = get_mod_time(path);
            if mod_t != info.mod_time {
                info.has_changed = true
            }
            return info.has_changed;
        }
        false
    }

    pub fn open(&mut self, path: &Path, id: BufferId) -> Result<Rope, FileError> {
        if !path.exists() {
            let _ = File::create(path).map_err(|e| FileError::Io(e, path.to_owned()))?;
        }

        let (rope, info) = try_load_file(path)?;

        self.open_files.insert(path.to_owned(), id);
        if self.file_info.insert(id, info).is_none() {
            #[cfg(feature = "notify")]
            self.watcher.watch(path, false, OPEN_FILE_EVENT_TOKEN);
        }
        Ok(rope)
    }

    pub fn close(&mut self, id: BufferId) {
        if let Some(info) = self.file_info.remove(&id) {
            self.open_files.remove(&info.path);
            #[cfg(feature = "notify")]
            self.watcher.unwatch(&info.path, OPEN_FILE_EVENT_TOKEN);
        }
    }

    pub fn save(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        let is_existing = self.file_info.contains_key(&id);
        if is_existing {
            self.save_existing(path, text, id)
        } else {
            self.save_new(path, text, id)
        }
    }

    fn save_new(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        try_save(path, text, CharacterEncoding::Utf8)
            .map_err(|e| FileError::Io(e, path.to_owned()))?;
        let info = FileInfo {
            encoding: CharacterEncoding::Utf8,
            path: path.to_owned(),
            mod_time: get_mod_time(path),
            has_changed: false,
        };
        self.open_files.insert(path.to_owned(), id);
        self.file_info.insert(id, info);
        #[cfg(feature = "notify")]
        self.watcher.watch(path, false, OPEN_FILE_EVENT_TOKEN);
        Ok(())
    }

    fn save_existing(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        let prev_path = self.file_info[&id].path.clone();
        if prev_path != path {
            self.save_new(path, text, id)?;
            self.open_files.remove(&prev_path);
            #[cfg(feature = "notify")]
            self.watcher.unwatch(&prev_path, OPEN_FILE_EVENT_TOKEN);
        } else if self.file_info[&id].has_changed {
            return Err(FileError::HasChanged(path.to_owned()));
        } else {
            let encoding = self.file_info[&id].encoding;
            try_save(path, text, encoding).map_err(|e| FileError::Io(e, path.to_owned()))?;
            self.file_info.get_mut(&id).unwrap().mod_time = get_mod_time(path);
        }
        Ok(())
    }
}

fn try_load_file<P>(path: P) -> Result<(Rope, FileInfo), FileError>
where
    P: AsRef<Path>,
{
    // TODO: support for non-utf8
    // it's arguable that the rope crate should have file loading functionality
    let mut f =
        File::open(path.as_ref()).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
    let mod_time =
        f.metadata().map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?.modified().ok();
    let mut bytes = Vec::new();
    f.read_to_end(&mut bytes).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;

    let encoding = CharacterEncoding::guess(&bytes);
    let rope = try_decode(bytes, encoding, path.as_ref())?;
    let info = FileInfo { encoding, mod_time, path: path.as_ref().to_owned(), has_changed: false };
    Ok((rope, info))
}

fn try_save(path: &Path, text: &Rope, encoding: CharacterEncoding) -> io::Result<()> {
    use std::fs;

    let tmp_extension = path.extension()
        .and_then(|extension| extension.to_str())
        .map_or_else(|| "swp".to_string(),|extension| format!("{}.swp", extension));
    let tmp_path = &path.with_extension(tmp_extension);

    let mut f = File::create(tmp_path)?;
    match encoding {
        CharacterEncoding::Utf8WithBom => f.write_all(UTF8_BOM.as_bytes())?,
        CharacterEncoding::Utf8 => (),
    }

    for chunk in text.iter_chunks(..text.len()) {
        f.write_all(chunk.as_bytes())?;
    }

    fs::rename(tmp_path, path)?;

    Ok(())
}

fn try_decode(bytes: Vec<u8>, encoding: CharacterEncoding, path: &Path) -> Result<Rope, FileError> {
    match encoding {
        CharacterEncoding::Utf8 => Ok(Rope::from(
            str::from_utf8(&bytes).map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?,
        )),
        CharacterEncoding::Utf8WithBom => {
            let s = String::from_utf8(bytes)
                .map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?;
            Ok(Rope::from(&s[UTF8_BOM.len()..]))
        }
    }
}

impl CharacterEncoding {
    fn guess(s: &[u8]) -> Self {
        if s.starts_with(UTF8_BOM.as_bytes()) {
            CharacterEncoding::Utf8WithBom
        } else {
            CharacterEncoding::Utf8
        }
    }
}

/// Returns the modification timestamp for the file at a given path,
/// if present.
fn get_mod_time<P>(path: P) -> Option<SystemTime>
where
    P: AsRef<Path>,
{
    File::open(path).and_then(|f| f.metadata()).and_then(|meta| meta.modified()).ok()
}

impl From<FileError> for RemoteError {
    fn from(src: FileError) -> RemoteError {
        //TODO: when we migrate to using the failure crate for error handling,
        // this should return a better message
        let code = src.error_code();
        let message = src.to_string();
        RemoteError::custom(code, message, None)
    }
}

impl FileError {
    fn error_code(&self) -> i64 {
        match self {
            FileError::Io(_, _) => 5,
            FileError::UnknownEncoding(_) => 6,
            FileError::HasChanged(_) => 7,
        }
    }
}

impl fmt::Display for FileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            FileError::Io(ref e, ref p) => write!(f, "{}. File path: {:?}", e, p),
            FileError::UnknownEncoding(ref p) => write!(f, "Error decoding file: {:?}", p),
            FileError::HasChanged(ref p) => write!(
                f,
                "File has changed on disk. \
                 Please save elsewhere and reload the file. File path: {:?}",
                p
            ),
        }
    }
}
<commit_msg>rustfmt check<commit_after>// Copyright 2018 The xi-editor Authors.
//
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Interactions with the file system.

use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str;
use std::time::SystemTime;

use xi_rope::Rope;
use xi_rpc::RemoteError;

use tabs::BufferId;

#[cfg(feature = "notify")]
use tabs::OPEN_FILE_EVENT_TOKEN;
#[cfg(feature = "notify")]
use watcher::FileWatcher;

const UTF8_BOM: &str = "\u{feff}";

/// Tracks all state related to open files.
pub struct FileManager {
    open_files: HashMap<PathBuf, BufferId>,
    file_info: HashMap<BufferId, FileInfo>,
    /// A monitor of filesystem events, for things like reloading changed files.
    #[cfg(feature = "notify")]
    watcher: FileWatcher,
}

#[derive(Debug)]
pub struct FileInfo {
    pub encoding: CharacterEncoding,
    pub path: PathBuf,
    pub mod_time: Option<SystemTime>,
    pub has_changed: bool,
}

pub enum FileError {
    Io(io::Error, PathBuf),
    UnknownEncoding(PathBuf),
    HasChanged(PathBuf),
}

#[derive(Debug, Clone, Copy)]
pub enum CharacterEncoding {
    Utf8,
    Utf8WithBom,
}

impl FileManager {
    #[cfg(feature = "notify")]
    pub fn new(watcher: FileWatcher) -> Self {
        FileManager { open_files: HashMap::new(), file_info: HashMap::new(), watcher }
    }

    #[cfg(not(feature = "notify"))]
    pub fn new() -> Self {
        FileManager { open_files: HashMap::new(), file_info: HashMap::new() }
    }

    #[cfg(feature = "notify")]
    pub fn watcher(&mut self) -> &mut FileWatcher {
        &mut self.watcher
    }

    pub fn get_info(&self, id: BufferId) -> Option<&FileInfo> {
        self.file_info.get(&id)
    }

    pub fn get_editor(&self, path: &Path) -> Option<BufferId> {
        self.open_files.get(path).cloned()
    }

    /// Returns `true` if this file is open and has changed on disk.
    /// This state is stashed.
    pub fn check_file(&mut self, path: &Path, id: BufferId) -> bool {
        if let Some(info) = self.file_info.get_mut(&id) {
            let mod_t = get_mod_time(path);
            if mod_t != info.mod_time {
                info.has_changed = true
            }
            return info.has_changed;
        }
        false
    }

    pub fn open(&mut self, path: &Path, id: BufferId) -> Result<Rope, FileError> {
        if !path.exists() {
            let _ = File::create(path).map_err(|e| FileError::Io(e, path.to_owned()))?;
        }

        let (rope, info) = try_load_file(path)?;

        self.open_files.insert(path.to_owned(), id);
        if self.file_info.insert(id, info).is_none() {
            #[cfg(feature = "notify")]
            self.watcher.watch(path, false, OPEN_FILE_EVENT_TOKEN);
        }
        Ok(rope)
    }

    pub fn close(&mut self, id: BufferId) {
        if let Some(info) = self.file_info.remove(&id) {
            self.open_files.remove(&info.path);
            #[cfg(feature = "notify")]
            self.watcher.unwatch(&info.path, OPEN_FILE_EVENT_TOKEN);
        }
    }

    pub fn save(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        let is_existing = self.file_info.contains_key(&id);
        if is_existing {
            self.save_existing(path, text, id)
        } else {
            self.save_new(path, text, id)
        }
    }

    fn save_new(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        try_save(path, text, CharacterEncoding::Utf8)
            .map_err(|e| FileError::Io(e, path.to_owned()))?;
        let info = FileInfo {
            encoding: CharacterEncoding::Utf8,
            path: path.to_owned(),
            mod_time: get_mod_time(path),
            has_changed: false,
        };
        self.open_files.insert(path.to_owned(), id);
        self.file_info.insert(id, info);
        #[cfg(feature = "notify")]
        self.watcher.watch(path, false, OPEN_FILE_EVENT_TOKEN);
        Ok(())
    }

    fn save_existing(&mut self, path: &Path, text: &Rope, id: BufferId) -> Result<(), FileError> {
        let prev_path = self.file_info[&id].path.clone();
        if prev_path != path {
            self.save_new(path, text, id)?;
            self.open_files.remove(&prev_path);
            #[cfg(feature = "notify")]
            self.watcher.unwatch(&prev_path, OPEN_FILE_EVENT_TOKEN);
        } else if self.file_info[&id].has_changed {
            return Err(FileError::HasChanged(path.to_owned()));
        } else {
            let encoding = self.file_info[&id].encoding;
            try_save(path, text, encoding).map_err(|e| FileError::Io(e, path.to_owned()))?;
            self.file_info.get_mut(&id).unwrap().mod_time = get_mod_time(path);
        }
        Ok(())
    }
}

fn try_load_file<P>(path: P) -> Result<(Rope, FileInfo), FileError>
where
    P: AsRef<Path>,
{
    // TODO: support for non-utf8
    // it's arguable that the rope crate should have file loading functionality
    let mut f =
        File::open(path.as_ref()).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
    let mod_time =
        f.metadata().map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?.modified().ok();
    let mut bytes = Vec::new();
    f.read_to_end(&mut bytes).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;

    let encoding = CharacterEncoding::guess(&bytes);
    let rope = try_decode(bytes, encoding, path.as_ref())?;
    let info = FileInfo { encoding, mod_time, path: path.as_ref().to_owned(), has_changed: false };
    Ok((rope, info))
}

fn try_save(path: &Path, text: &Rope, encoding: CharacterEncoding) -> io::Result<()> {
    use std::fs;

    let tmp_extension = path
        .extension()
        .and_then(|extension| extension.to_str())
        .map_or_else(|| "swp".to_string(), |extension| format!("{}.swp", extension));
    let tmp_path = &path.with_extension(tmp_extension);

    let mut f = File::create(tmp_path)?;
    match encoding {
        CharacterEncoding::Utf8WithBom => f.write_all(UTF8_BOM.as_bytes())?,
        CharacterEncoding::Utf8 => (),
    }

    for chunk in text.iter_chunks(..text.len()) {
        f.write_all(chunk.as_bytes())?;
    }

    fs::rename(tmp_path, path)?;

    Ok(())
}

fn try_decode(bytes: Vec<u8>, encoding: CharacterEncoding, path: &Path) -> Result<Rope, FileError> {
    match encoding {
        CharacterEncoding::Utf8 => Ok(Rope::from(
            str::from_utf8(&bytes).map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?,
        )),
        CharacterEncoding::Utf8WithBom => {
            let s = String::from_utf8(bytes)
                .map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?;
            Ok(Rope::from(&s[UTF8_BOM.len()..]))
        }
    }
}

impl CharacterEncoding {
    fn guess(s: &[u8]) -> Self {
        if s.starts_with(UTF8_BOM.as_bytes()) {
            CharacterEncoding::Utf8WithBom
        } else {
            CharacterEncoding::Utf8
        }
    }
}

/// Returns the modification timestamp for the file at a given path,
/// if present.
fn get_mod_time<P>(path: P) -> Option<SystemTime>
where
    P: AsRef<Path>,
{
    File::open(path).and_then(|f| f.metadata()).and_then(|meta| meta.modified()).ok()
}

impl From<FileError> for RemoteError {
    fn from(src: FileError) -> RemoteError {
        //TODO: when we migrate to using the failure crate for error handling,
        // this should return a better message
        let code = src.error_code();
        let message = src.to_string();
        RemoteError::custom(code, message, None)
    }
}

impl FileError {
    fn error_code(&self) -> i64 {
        match self {
            FileError::Io(_, _) => 5,
            FileError::UnknownEncoding(_) => 6,
            FileError::HasChanged(_) => 7,
        }
    }
}

impl fmt::Display for FileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            FileError::Io(ref e, ref p) => write!(f, "{}. File path: {:?}", e, p),
            FileError::UnknownEncoding(ref p) => write!(f, "Error decoding file: {:?}", p),
            FileError::HasChanged(ref p) => write!(
                f,
                "File has changed on disk. \
                 Please save elsewhere and reload the file. File path: {:?}",
                p
            ),
        }
    }
}
<|endoftext|>


<commit_before>///Script to calculate checksums
extern crate crypto;

use crypto::digest::Digest;
use crypto::md5::Md5;
use crypto::sha1::Sha1;
use crypto::sha2::{Sha256, Sha512};

use std::env::args as cmd_args;
use std::fs::{read_dir, metadata};
use std::io::{Read, Write, BufReader};

#[inline(always)]
fn usage() {
println!("Usage: hash_sum [-c | -o | -p] [options] <input>...

Options:
    --md5       Enables md5 calculation.
    --sha1      Enables sha1 calculation.
    --sha256    Enables sha256 calculation.
    --sha512    Enables sha512 calculation.

Flags:
    Flags are mutually exclusive.
    -p/--print  Prints checksums to stdout. Default.
    -c/--check  Verifies checksum from files.
    -o/--output Write calculations into files with corresponding extension.
");
}

//Buffer size 10kbs
const BUFFER_SIZE: usize = 10000;

macro_rules! is_file {
    ($path:expr) => { std::fs::metadata($path).unwrap().is_file() }
}

macro_rules! input {
    ($msg:expr) => {{
        use std::io::{Read, Write};
        print!($msg);
        std::io::stdout().flush().unwrap();
        std::io::stdin().bytes().next();
    }}
}

struct Checksum(String, Box<Digest>);

impl Checksum {
    #[inline(always)]
    pub fn input(&mut self, slice_content: &[u8]) { self.1.input(slice_content); }
    #[inline(always)]
    pub fn result(&mut self) -> String { format!("{:8}{}", self.0, self.1.result_str()) }
    #[inline(always)]
    pub fn get_checksum(&mut self) -> String { self.1.result_str() }
    #[inline(always)]
    pub fn reset(&mut self) { self.1.reset(); }
    #[inline(always)]
    pub fn get_file_ext(&self) -> String { self.0[0..self.0.len()-1].chars().map(|elem| elem.to_lowercase().next().unwrap()).collect() }
    #[inline(always)]
    pub fn get_type_string(&self) -> String { format!("{:8}", self.0) }
}

impl PartialEq for Checksum {
    fn eq(&self, right: &Checksum) -> bool {
        self.0 == right.0
    }

    fn ne(&self, right: &Checksum) -> bool {
        self.0 != right.0
    }
}

enum FlagType {
    Print,
    Output,
    Check,
}

struct HashSum(Vec<String>, Vec<Checksum>, FlagType);

impl HashSum {
    #[inline(always)]
    fn default_algos(&mut self) {
        self.1.push(Checksum("MD5:".to_string(), Box::new(Md5::new())));
        self.1.push(Checksum("SHA1:".to_string(), Box::new(Sha1::new())));
        self.1.push(Checksum("SHA256:".to_string(), Box::new(Sha256::new())));
        self.1.push(Checksum("SHA512:".to_string(), Box::new(Sha512::new())));
    }

    #[inline(always)]
    fn reset_algos(&mut self) {
        for algo in self.1.iter_mut() {
            algo.reset();
        }
    }

    pub fn run_from_args() {
        let mut hash_sum = HashSum(Vec::new(), Vec::new(), FlagType::Print);

        for arg in cmd_args().skip(1) {
            if arg.starts_with("-") {
                match arg.as_ref() {
                    "--md5" => hash_sum.1.push(Checksum("MD5:".to_string(), Box::new(Md5::new()) as Box<Digest>)),
                    "--sha1" => hash_sum.1.push(Checksum("SHA1:".to_string(), Box::new(Sha1::new()) as Box<Digest>)),
                    "--sha256" => hash_sum.1.push(Checksum("SHA256:".to_string(), Box::new(Sha256::new()) as Box<Digest>)),
                    "--sha512" => hash_sum.1.push(Checksum("SHA512:".to_string(), Box::new(Sha512::new()) as Box<Digest>)),
                    "-o" | "--output" => hash_sum.2 = FlagType::Output,
                    "-c" | "--check" => hash_sum.2 = FlagType::Check,
                    "-p" | "--print" => hash_sum.2 = FlagType::Print,
                    arg @ _ => println!(">>>Invalid flag {}", arg),
                }
            }
            else { hash_sum.0.push(arg) }
        }

        if hash_sum.1.len() == 0 { hash_sum.default_algos(); }

        if hash_sum.0.len() == 0 {
            println!(">>>No input is given!");
            return;
        }

        hash_sum.run()
    }

    fn run(&mut self) {
        for arg in self.0.clone() {
            if let Ok(arg_meta_data) = metadata(&arg) {
                if arg_meta_data.is_file() {
                    self.calc(&arg);
                }
                else if let Ok(dir_content) = read_dir(&arg) {
                    for entry_path in dir_content.map(|entry| entry.unwrap().path()) {
                        if is_file!(&entry_path) { self.calc(&entry_path.to_str().unwrap()); }
                    }
                }
                else {
                    println!(">>>{}: cannot access content of this dir", &arg);
                    continue;
                }
            }
            else {
                println!(">>>{}: cannot access file/directory", &arg);
                continue;
            }
        }
    }

    fn calc(&mut self, path: &str) {
        self.reset_algos();
        if let Ok(file) = std::fs::File::open(&path) {
            let mut file = BufReader::new(file);
            println!(">>>{}:", &path);
            loop {
                let mut file_content: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
                let read_bytes = file.read(&mut file_content).unwrap();
                if read_bytes == 0 { break; }

                //In case if read is unable to fill buffer completely.
                let slice_content = &file_content[..read_bytes];
                for algo in self.1.iter_mut() {
                    algo.input(slice_content);
                }
            }

            for algo in self.1.iter_mut() {
                match self.2 {
                    FlagType::Output => {
                        let file_name = format!("{}.{}", &path, algo.get_file_ext());
                        if let Ok(mut file) = std::fs::File::create(&file_name) {
                            file.write_fmt(format_args!("{}\n", algo.get_checksum())).unwrap();
                            println!("{}{}", algo.get_type_string(), &file_name);
                        }
                        else {
                            println!("{}Unable to create file with checksum!", algo.get_type_string());
                        }
                    },
                    FlagType::Check => {
                        let file_name = format!("{}.{}", &path, algo.get_file_ext());
                        if let Ok(mut file) = std::fs::File::open(&file_name) {
                            let mut expected_checksum = String::new();
                            if file.read_to_string(&mut expected_checksum).is_ok() {
                                if expected_checksum.trim() == algo.get_checksum() {
                                    println!("{}OK", algo.get_type_string());
                                }
                                else {
                                    println!("{}NOT_OK", algo.get_type_string());
                                }
                            }
                            else {
                                println!("{}Failed to get checksum from file!", algo.get_type_string());
                            }
                        }
                        else {
                            println!("{}No checksum file!", algo.get_type_string());
                        }
                    },
                    FlagType::Print => {
                        println!("{}", algo.result());
                    },

                }
            }
            println!("=======================================================\n");
        }
        else {
            println!(">>>{}: failed to open", &path);
        }
    }
}

fn main() {
    if cmd_args().len() < 2 { return usage(); }

    HashSum::run_from_args();

    //this is done mostly for convenient drag and drop.
    if cfg!(windows) {
        input!("Press Enter to exit...");
    }
}
<commit_msg>Rust: hash_sum upgrade cmd handling<commit_after>///Script to calculate checksums
extern crate crypto;

use crypto::digest::Digest;
use crypto::md5::Md5;
use crypto::sha1::Sha1;
use crypto::sha2::{Sha256, Sha512};

use std::env::args as cmd_args;
use std::fs::{read_dir, metadata};
use std::io::{Read, Write, BufReader};

#[inline(always)]
fn usage() {
println!("Usage: hash_sum [-c | -o | -p] [algorithms] <input>...

Algorithms:
    --md5       Enables md5 calculation.
    --sha[num]  Enables sha calculation. num can be [1, 256, 512]

Mode:
    Mutually exclusive.
    -c --check  Verifies checksum from files.
    -o --output Write calculations into files with corresponding extension.
    -p --print  Prints checksums to stdout. Default.
");
}

fn invalid_usage(text: &str) {
    println!("{}", text);
    usage();
}

//Buffer size 10kbs
const BUFFER_SIZE: usize = 10000;

macro_rules! is_file {
    ($path:expr) => { std::fs::metadata($path).unwrap().is_file() }
}

macro_rules! input {
    ($msg:expr) => {{
        use std::io::{Read, Write};
        print!($msg);
        std::io::stdout().flush().unwrap();
        std::io::stdin().bytes().next();
    }}
}

struct Checksum(String, Box<Digest>);

impl Checksum {
    #[inline(always)]
    pub fn input(&mut self, slice_content: &[u8]) { self.1.input(slice_content); }
    #[inline(always)]
    pub fn result(&mut self) -> String { format!("{:8}{}", self.0, self.1.result_str()) }
    #[inline(always)]
    pub fn get_checksum(&mut self) -> String { self.1.result_str() }
    #[inline(always)]
    pub fn reset(&mut self) { self.1.reset(); }
    #[inline(always)]
    pub fn get_file_ext(&self) -> String { self.0[0..self.0.len()-1].chars().map(|elem| elem.to_lowercase().next().unwrap()).collect() }
    #[inline(always)]
    pub fn get_type_string(&self) -> String { format!("{:8}", self.0) }
}

impl PartialEq for Checksum {
    fn eq(&self, right: &Checksum) -> bool {
        self.0 == right.0
    }

    fn ne(&self, right: &Checksum) -> bool {
        self.0 != right.0
    }
}

enum FlagType {
    Print,
    Output,
    Check,
}

struct HashSum(Vec<String>, Vec<Checksum>, FlagType);

impl HashSum {
    #[inline(always)]
    fn default_algos(&mut self) {
        self.1.push(Checksum("MD5:".to_string(), Box::new(Md5::new())));
        self.1.push(Checksum("SHA1:".to_string(), Box::new(Sha1::new())));
        self.1.push(Checksum("SHA256:".to_string(), Box::new(Sha256::new())));
        self.1.push(Checksum("SHA512:".to_string(), Box::new(Sha512::new())));
    }

    #[inline(always)]
    fn reset_algos(&mut self) {
        for algo in self.1.iter_mut() {
            algo.reset();
        }
    }

    fn sha_match(&mut self, arg: &str) -> bool {
        match arg {
            "1" => self.1.push(Checksum("SHA1:".to_string(), Box::new(Sha1::new()) as Box<Digest>)),
            "256" => self.1.push(Checksum("SHA256:".to_string(), Box::new(Sha256::new()) as Box<Digest>)),
            "512" => self.1.push(Checksum("SHA512:".to_string(), Box::new(Sha512::new()) as Box<Digest>)),
            arg @ _ => { println!(">>>Invalid option --sha{}", arg); return false; },
        }

        true
    }

    pub fn run_from_args() {
        let mut hash_sum = HashSum(Vec::new(), Vec::new(), FlagType::Print);

        for arg in cmd_args().skip(1) {
            if arg.starts_with("-") {
                match arg.as_ref() {
                    "--md5" => hash_sum.1.push(Checksum("MD5:".to_string(), Box::new(Md5::new()) as Box<Digest>)),
                    arg if arg.starts_with("--sha") => if !hash_sum.sha_match(&arg[5..]) { return usage(); },
                    "-o" | "--output" => hash_sum.2 = FlagType::Output,
                    "-c" | "--check" => hash_sum.2 = FlagType::Check,
                    "-p" | "--print" => hash_sum.2 = FlagType::Print,
                    arg @ _ => return invalid_usage(&format!(">>>Invalid option {}", arg)),
                }
            }
            else { hash_sum.0.push(arg) }
        }

        if hash_sum.0.len() == 0 {
            println!(">>>No input is given!");
            return;
        }

        if hash_sum.1.len() == 0 { hash_sum.default_algos(); }

        hash_sum.run()
    }

    fn run(&mut self) {
        for arg in self.0.clone() {
            if let Ok(arg_meta_data) = metadata(&arg) {
                if arg_meta_data.is_file() {
                    self.calc(&arg);
                }
                else if let Ok(dir_content) = read_dir(&arg) {
                    for entry_path in dir_content.map(|entry| entry.unwrap().path()) {
                        if is_file!(&entry_path) { self.calc(&entry_path.to_str().unwrap()); }
                    }
                }
                else {
                    println!(">>>{}: cannot access content of this dir", &arg);
                    continue;
                }
            }
            else {
                println!(">>>{}: cannot access file/directory", &arg);
                continue;
            }
        }
    }

    fn calc(&mut self, path: &str) {
        self.reset_algos();
        if let Ok(file) = std::fs::File::open(&path) {
            let mut file = BufReader::new(file);
            println!(">>>{}:", &path);
            loop {
                let mut file_content: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
                let read_bytes = file.read(&mut file_content).unwrap();
                if read_bytes == 0 { break; }

                //In case if read is unable to fill buffer completely.
                let slice_content = &file_content[..read_bytes];
                for algo in self.1.iter_mut() {
                    algo.input(slice_content);
                }
            }

            for algo in self.1.iter_mut() {
                match self.2 {
                    FlagType::Output => {
                        let file_name = format!("{}.{}", &path, algo.get_file_ext());
                        if let Ok(mut file) = std::fs::File::create(&file_name) {
                            file.write_fmt(format_args!("{}\n", algo.get_checksum())).unwrap();
                            println!("{}{}", algo.get_type_string(), &file_name);
                        }
                        else {
                            println!("{}Unable to create file with checksum!", algo.get_type_string());
                        }
                    },
                    FlagType::Check => {
                        let file_name = format!("{}.{}", &path, algo.get_file_ext());
                        if let Ok(mut file) = std::fs::File::open(&file_name) {
                            let mut expected_checksum = String::new();
                            if file.read_to_string(&mut expected_checksum).is_ok() {
                                if expected_checksum.trim() == algo.get_checksum() {
                                    println!("{}OK", algo.get_type_string());
                                }
                                else {
                                    println!("{}NOT_OK", algo.get_type_string());
                                }
                            }
                            else {
                                println!("{}Failed to get checksum from file!", algo.get_type_string());
                            }
                        }
                        else {
                            println!("{}No checksum file!", algo.get_type_string());
                        }
                    },
                    FlagType::Print => {
                        println!("{}", algo.result());
                    },

                }
            }
            println!("=======================================================\n");
        }
        else {
            println!(">>>{}: failed to open", &path);
        }
    }
}

fn main() {
    if cmd_args().len() < 2 { return usage(); }

    HashSum::run_from_args();

    //this is done mostly for convenient drag and drop.
    if cfg!(windows) {
        input!("Press Enter to exit...");
    }
}
<|endoftext|>


<commit_before>// ! Spit string with a delimeter.

// xxx

pub( crate ) mod internal
{

  /* xxx : qqq : tab after git sync */

// use woptions::*; /* xxx : use prelude */

///
/// Either delimeter or delimeted with the slice on its string.
///

#[allow(dead_code)]
#[ derive( Debug ) ]
pub struct Split< 'a >
{
  string : &'a str,
  typ : SplitType,
}

impl< 'a > From< Split< 'a > > for String
{
  fn from( src : Split ) -> Self
  {
    src.string.into()
  }
}

///
/// Either delimeter or delimeted
///

#[ derive( Debug ) ]
pub enum SplitType
{
  /// Substring of the original string with text inbetween delimeters.
  Delimeted,
  /// Delimeter.
  Delimeter,
}

///
/// Find first match in the string.
///

pub trait Searcher
{
  /// Find positions of delimeter.
  fn pos( &self, src : &str ) -> Option< ( usize, usize ) >;
}

impl Searcher for &str
{
  fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
  {
    src.find( self ).map( | start | ( start, start + self.len() ) )
  }
}

impl Searcher for String
{
  fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
  {
    src.find( self ).map( | start | ( start, start + self.len() ) )
  }
}

impl Searcher for Vec<&str>
{
  fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
  {
    let mut r = vec![];
    for pat in self
    {
      match src.find( pat )
      {
        Some( x ) => r.push( ( x, x + pat.len() ) ),
        None => (),
      }
    }

    if r.is_empty()
    {
      return None;
    }

    r.into_iter().reduce( | accum, item |
    {
      if accum.0 > item.0
      {
        item
      }
      else
      {
        if accum.1 > item.1
        {
          item
        }
        else
        {
          accum
        }
      }
    })
  }
}

///
/// Split iterator.
///

#[ derive( Debug ) ]
pub struct SplitIterator< 'a, D >
where
  D : Searcher
{
  iterable : &'a str,
  counter : i32,
  delimeter : D,
  preserving_empty : bool,
  preserving_delimeters : bool,
  stripping : bool,
  stop_empty : bool,
}

//

impl< 'a, D : Searcher > SplitIterator< 'a, D >
{
  fn new
  (
    src : &'a str,
    delimeter : D,
    preserving_empty : bool,
    preserving_delimeters : bool,
    stripping : bool
  ) -> Self
  {
    Self
    {
      iterable : src,
      delimeter,
      counter : 0,
      preserving_empty,
      preserving_delimeters,
      stripping,
      stop_empty : false,
    }
  }
}

//

impl< 'a, D > Iterator for SplitIterator< 'a, D >
where
  D : Searcher
{
  type Item = Split< 'a >;

  fn next( &mut self ) -> Option< Self::Item >
  {
    self.counter += 1;

    if self.counter % 2 == 1
    {
      let positions = self.delimeter.pos( self.iterable );
      if let Some( ( mut start, end ) ) = positions
      {
        if self.iterable == "" && start == end
        {
          if self.stop_empty
          {
            return None;
          }
          else
          {
            self.counter -= 1;
            self.stop_empty = true;
            return Some( Split { string : "", typ : SplitType::Delimeted } );
          }
        }
        let mut next = &self.iterable[ ..start ];
        if start == end
        {
          if self.counter >= 3
          {
            next = &self.iterable[ ..start + 1 ];
            start += 1;
          }
        }
        if self.stripping
        {
          next = next.trim();
        }

        self.iterable = &self.iterable[ start.. ];

        Some( Split { string : next, typ : SplitType::Delimeted } )
      }
      else
      {
        return self.next_end_split();
      }
    }
    else
    {
      if self.delimeter.pos( self.iterable ).is_none()
      {
        self.iterable = "";
        return None;
      }

      let ( start, end ) = self.delimeter.pos( self.iterable ).unwrap();
      let mut string = &self.iterable[ start..end ];
      self.iterable = &self.iterable[ end.. ];

      if self.stripping
      {
        string = string.trim();

        if string.is_empty() && !self.preserving_empty
        {
          return self.next_odd_split();
        }
      }

      if self.preserving_delimeters
      {
        return Some( Split { string, typ : SplitType::Delimeter } );
      }
      else
      {
        return self.next_odd_split();
      }
    }
  }
}

impl< 'a, D > SplitIterator< 'a, D >
where
  D : Searcher
{
  fn next_end_split( &mut self ) -> Option< Split< 'a > >
  {
    if self.iterable == ""
    {
      return None;
    }
    else
    {
      let mut string = self.iterable;
      if self.stripping
      {
        string = string.trim();
      }
      let r = Split { string, typ : SplitType::Delimeted };
      self.iterable = "";
      return Some( r );
    }
  }

  fn next_odd_split( &mut self ) -> Option< Split< 'a > >
  {
    match self.delimeter.pos( self.iterable )
    {
      Some( ( start, mut end ) ) =>
      {
        let mut string = &self.iterable[ ..start ];

        if start == end
        {
          string = &self.iterable[ ..start + 1 ];
          end += 1;
        }
        self.iterable = &self.iterable[ end.. ];
        if self.stripping
        {
          string = string.trim();
        }
        return Some( Split { string, typ : SplitType::Delimeted } );
      },
      None =>
      {
        self.next_end_split()
      },
    }
  }
}

///
/// Options of function split.
///

#[ derive( Debug ) ]
pub struct SplitOptions< 'a, D >
where
  D : Searcher + Default + Clone
{
  src : &'a str,
  delimeter : D,
  preserving_empty : bool,
  preserving_delimeters : bool,
  stripping : bool,
}

//

macro_rules! builder_impls_from
{
  ( $name : ident, $( ( $field : ident, $type : ty ) ),* $( , )? ) =>
  {
    impl< 'a, D > $name< 'a, D >
    where
      D : Searcher + Default + Clone
    {
      $(
        pub fn $field( mut self, value : $type ) -> $name< 'a, D >
        {
          assert!( !self.formed, "Already formed" );
          self.$field = value;
          self
        }
      )*

      pub fn form( mut self ) -> $name< 'a, D >
      {
        assert!( !self.formed, "Already formed" );
        self.formed = true;
        self
      }
    }
  }
}

/// Builder.
#[ derive( Debug ) ]
pub struct SplitOptionsBuilder< 'a, D >
where
  D : Searcher + Default + Clone
{

  src : &'a str,
  delimeter : D,
  preserving_empty : bool,
  preserving_delimeters : bool,
  stripping : bool,
  formed : bool,
}
builder_impls_from!( SplitOptionsBuilder, ( src, &'a str ), ( preserving_empty, bool ), ( preserving_delimeters, bool ), ( stripping, bool ) );

impl< 'a, D > SplitOptionsBuilder< 'a, D >
where
  D : Searcher + Default + Clone
{
  pub fn new( delimeter : D ) -> SplitOptionsBuilder< 'a, D >
  {
    Self
    {
      src : "",
      delimeter,
      preserving_empty : true,
      preserving_delimeters : true,
      stripping : true,
      formed : false,
    }
  }

  pub fn delimeter( mut self, value : D ) -> SplitOptionsBuilder< 'a, D >
  {
    assert!( !self.formed, "Already formed" );
    self.delimeter = value;
    self
  }

  pub fn perform( &mut self ) -> SplitIterator< 'a, D >
  {
    if !self.formed
    {
      self.formed = true;
    }

    let opts = SplitOptions
    {
      src : self.src,
      delimeter : self.delimeter.clone(),
      preserving_empty : self.preserving_empty,
      preserving_delimeters : self.preserving_delimeters,
      stripping : self.stripping,
    };
    opts.split()
  }
}

///
/// Adapter for Split Options.
///

pub trait SplitOptionsAdapter< 'a, D >
{
  /// A string to split.
  fn src( &self ) -> &'a str;
  /// A delimeter to split string.
  fn delimeter( &self ) -> D;
  /// Preserving or dropping empty splits.
  fn preserving_empty( &self ) -> bool;
  /// Preserving or dropping delimeters.
  fn preserving_delimeters( &self ) -> bool;
  /// Stripping.
  fn stripping( &self ) -> bool;
  /// Do splitting.
  fn split( self ) -> SplitIterator< 'a, D >
  where
    Self : Sized,
    D : Searcher + Default,
  {
    SplitIterator::new
    (
      self.src(),
      self.delimeter(),
      self.preserving_empty(),
      self.preserving_delimeters(),
      self.stripping()
    )
  }
}

//

impl< 'a, D > SplitOptionsAdapter< 'a, D > for SplitOptions< 'a, D >
where
  D : Searcher + Default + Clone,
{
  fn src( &self ) -> &'a str
  {
    self.src
  }
  fn delimeter( &self ) -> D
  {
    self.delimeter.clone()
  }
  fn preserving_empty( &self ) -> bool
  {
    self.preserving_empty
  }
  fn preserving_delimeters( &self ) -> bool
  {
    self.preserving_delimeters
  }
  fn stripping( &self ) -> bool
  {
    self.stripping
  }
}

///
/// Function to split a string.
///
/// It produces former. To convert former into options and run algorithm of splitting call `form()`.
///
/// # Sample
/// ```
///   let iter = wstring_tools::string::split()
///   .src( "abc def" )
///   .delimeter( " " )
///   .perform();
/// ```

pub fn split< 'a >() -> SplitOptionsBuilder< 'a, &'a str >
{
  SplitOptionsBuilder::new( < &str >::default() )
}

///

pub fn split_generic< 'a, D : Searcher + Default + Clone >() -> SplitOptionsBuilder< 'a, D >
{
  SplitOptionsBuilder::new( <D>::default() )
}

}

/// Owned namespace of the module.
pub mod own
{
  use super::internal as i;

  pub use i::Split;
  pub use i::SplitType;
  pub use i::SplitIterator;
  pub use i::SplitOptions;
  pub use i::SplitOptionsAdapter;
  pub use i::split;
  pub use i::split_generic;
}

pub use own::*;

/// Exposed namespace of the module.
pub mod exposed
{
  use super::internal as i;

  pub use i::SplitOptionsAdapter;
  pub use i::split;
  pub use i::split_generic;
}

/// Namespace of the module to include with `use module::*`.
pub mod prelude
{
  use super::internal as i;

  pub use i::SplitOptionsAdapter;
}
<commit_msg>Improve module `split`, split iterator on `SplitFastIterator` and `SplitIterator`, use options separately<commit_after>// ! Spit string with a delimeter.

// xxx

pub( crate ) mod internal
{

  /* xxx : qqq : tab after git sync */

  // use woptions::*; /* xxx : use prelude */

  ///
  /// Either delimeter or delimeted with the slice on its string.
  ///

  #[allow(dead_code)]
  #[ derive( Debug ) ]
  pub struct Split< 'a >
  {
    string : &'a str,
    typ : SplitType,
  }

  impl< 'a > From< Split< 'a > > for String
  {
    fn from( src : Split ) -> Self
    {
      src.string.into()
    }
  }

  ///
  /// Either delimeter or delimeted
  ///

  #[ derive( Debug ) ]
  pub enum SplitType
  {
    /// Substring of the original string with text inbetween delimeters.
    Delimeted,
    /// Delimeter.
    Delimeter,
  }

  ///
  /// Find first match in the string.
  ///

  pub trait Searcher
  {
    /// Find positions of delimeter.
    fn pos( &self, src : &str ) -> Option< ( usize, usize ) >;
  }

  impl Searcher for &str
  {
    fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
    {
      src.find( self ).map( | start | ( start, start + self.len() ) )
    }
  }

  impl Searcher for String
  {
    fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
    {
      src.find( self ).map( | start | ( start, start + self.len() ) )
    }
  }

  impl Searcher for Vec<&str>
  {
    fn pos( &self, src : &str ) -> Option< ( usize, usize ) >
    {
      let mut r = vec![];
      for pat in self
      {
        match src.find( pat )
        {
          Some( x ) => r.push( ( x, x + pat.len() ) ),
          None => (),
        }
      }

      if r.is_empty()
      {
        return None;
      }

      r.into_iter().reduce( | accum, item |
      {
        if accum.0 > item.0
        {
          item
        }
        else
        {
          if accum.1 > item.1
          {
            item
          }
          else
          {
            accum
          }
        }
      })
    }
  }

  ///
  /// Split iterator.
  ///

  #[ derive( Debug ) ]
  pub struct SplitFastIterator< 'a, D >
  where
    D : Searcher
  {
    iterable : &'a str,
    counter : i32,
    delimeter : D,
    preserving_empty : bool,
    preserving_delimeters : bool,
    stop_empty : bool,
  }

  //

  impl< 'a, D : Searcher > SplitFastIterator< 'a, D >
  {
    fn new
    (
      src : &'a str,
      delimeter : D,
      preserving_empty : bool,
      preserving_delimeters : bool,
    ) -> Self
    {
      Self
      {
        iterable : src,
        delimeter,
        counter : 0,
        preserving_empty,
        preserving_delimeters,
        stop_empty : false,
      }
    }
  }

  //

  impl< 'a, D > Iterator for SplitFastIterator< 'a, D >
  where
    D : Searcher
  {
    type Item = Split< 'a >;

    fn next( &mut self ) -> Option< Self::Item >
    {
      self.counter += 1;

      if self.counter % 2 == 1
      {
        let positions = self.delimeter.pos( self.iterable );
        if let Some( ( mut start, end ) ) = positions
        {
          if self.iterable == "" && start == end
          {
            if self.stop_empty
            {
              return None;
            }
            else
            {
              self.counter -= 1;
              self.stop_empty = true;
              return Some( Split { string : "", typ : SplitType::Delimeted } );
            }
          }
          let mut next = &self.iterable[ ..start ];
          if start == end
          {
            if self.counter >= 3
            {
              next = &self.iterable[ ..start + 1 ];
              start += 1;
            }
          }

          self.iterable = &self.iterable[ start.. ];

          Some( Split { string : next, typ : SplitType::Delimeted } )
        }
        else
        {
          return self.next_end_split();
        }
      }
      else
      {
        if self.delimeter.pos( self.iterable ).is_none()
        {
          self.iterable = "";
          return None;
        }

        let ( start, end ) = self.delimeter.pos( self.iterable ).unwrap();
        let mut string = &self.iterable[ start..end ];
        self.iterable = &self.iterable[ end.. ];

        if self.preserving_delimeters
        {
          return Some( Split { string, typ : SplitType::Delimeter } );
        }
        else
        {
          return self.next_odd_split();
        }
      }
    }
  }

  impl< 'a, D > SplitFastIterator< 'a, D >
  where
    D : Searcher
  {
    fn next_end_split( &mut self ) -> Option< Split< 'a > >
    {
      if self.iterable == ""
      {
        return None;
      }
      else
      {
        let mut string = self.iterable;
        let r = Split { string, typ : SplitType::Delimeted };
        self.iterable = "";
        return Some( r );
      }
    }

    fn next_odd_split( &mut self ) -> Option< Split< 'a > >
    {
      match self.delimeter.pos( self.iterable )
      {
        Some( ( start, mut end ) ) =>
        {
          let mut string = &self.iterable[ ..start ];

          if start == end
          {
            string = &self.iterable[ ..start + 1 ];
            end += 1;
          }
          self.iterable = &self.iterable[ end.. ];
          return Some( Split { string, typ : SplitType::Delimeted } );
        },
        None =>
        {
          self.next_end_split()
        },
      }
    }
  }

  ///
  /// Split iterator.
  ///

  #[ derive( Debug ) ]
  pub struct SplitIterator< 'a, D >
  where
    D : Searcher
  {
    iterator : SplitFastIterator< 'a, D >,
    counter : usize,
    stripping : bool,
  }

  //

  impl< 'a, D : Searcher > SplitIterator< 'a, D >
  {
    fn new
    (
      src : &'a str,
      delimeter : D,
      preserving_empty : bool,
      preserving_delimeters : bool,
      stripping : bool,
    ) -> Self
    {
      let iterator = SplitFastIterator
      {
        iterable : src,
        delimeter,
        counter : 0,
        preserving_empty,
        preserving_delimeters,
        stop_empty : false,
      };

      Self
      {
        iterator,
        counter : 0,
        stripping,
      }
    }
  }

  impl< 'a, D > Iterator for SplitIterator< 'a, D >
  where
    D : Searcher
  {
    type Item = Split< 'a >;

    fn next( &mut self ) -> Option< Self::Item >
    {
      let mut split = self.iterator.next();
      self.counter += 1;

      if let Some( mut split ) = split
      {
        if self.stripping
        {
          split.string = split.string.trim();
          if !self.iterator.preserving_empty && split.string.is_empty() && self.counter % 2 == 0
          {
            self.counter += 1;
            return self.iterator.next();
          }
        }
        Some( split )
      }
      else
      {
        None
      }
    }
  }

  ///
  /// Options of function split.
  ///

  #[ derive( Debug ) ]
  pub struct SplitOptions< 'a, D >
  where
    D : Searcher + Default + Clone
  {
    src : &'a str,
    delimeter : D,
    preserving_empty : bool,
    preserving_delimeters : bool,
    stripping : bool,
  }

  ///
  /// Adapter for Split Options.
  ///

  pub trait SplitOptionsAdapter< 'a, D >
  {
    /// A string to split.
    fn src( &self ) -> &'a str;
    /// A delimeter to split string.
    fn delimeter( &self ) -> D;
    /// Preserving or dropping empty splits.
    fn preserving_empty( &self ) -> bool;
    /// Preserving or dropping delimeters.
    fn preserving_delimeters( &self ) -> bool;
    /// Stripping.
    fn stripping( &self ) -> bool;
    /// Do splitting.
    fn split( self ) -> SplitIterator< 'a, D >
    where
      Self : Sized,
      D : Searcher + Default,
    {
      SplitIterator::new
      (
        self.src(),
        self.delimeter(),
        self.preserving_empty(),
        self.preserving_delimeters(),
        self.stripping()
      )
    }
  }

  //

  impl< 'a, D > SplitOptionsAdapter< 'a, D > for SplitOptions< 'a, D >
  where
    D : Searcher + Default + Clone,
  {
    fn src( &self ) -> &'a str
    {
      self.src
    }
    fn delimeter( &self ) -> D
    {
      self.delimeter.clone()
    }
    fn preserving_empty( &self ) -> bool
    {
      self.preserving_empty
    }
    fn preserving_delimeters( &self ) -> bool
    {
      self.preserving_delimeters
    }
    fn stripping( &self ) -> bool
    {
      self.stripping
    }
  }

  //

  macro_rules! builder_impls_from
  {
    ( $name : ident, $( ( $field : ident, $type : ty ) ),* $( , )? ) =>
    {
      impl< 'a, D > $name< 'a, D >
      where
        D : Searcher + Default + Clone
      {
        $(
          pub fn $field( mut self, value : $type ) -> $name< 'a, D >
          {
            assert!( !self.formed, "Already formed" );
            self.$field = value;
            self
          }
        )*

        pub fn form( mut self ) -> $name< 'a, D >
        {
          assert!( !self.formed, "Already formed" );
          self.formed = true;
          self
        }
      }
    }
  }

  ///
  /// Builder for SplitOptions.
  ///

  #[ derive( Debug ) ]
  pub struct SplitOptionsBuilder< 'a, D >
  where
    D : Searcher + Default + Clone
  {
    src : &'a str,
    delimeter : D,
    preserving_empty : bool,
    preserving_delimeters : bool,
    stripping : bool,
    formed : bool,
  }
  builder_impls_from!
  (
    SplitOptionsBuilder,
    ( src, &'a str ),
    ( delimeter, D ),
    ( preserving_empty, bool ),
    ( preserving_delimeters, bool ),
    ( stripping, bool )
  );

  impl< 'a, D > SplitOptionsBuilder< 'a, D >
  where
    D : Searcher + Default + Clone
  {
    pub fn new( delimeter : D ) -> SplitOptionsBuilder< 'a, D >
    {
      Self
      {
        src : "",
        delimeter,
        preserving_empty : true,
        preserving_delimeters : true,
        stripping : true,
        formed : false,
      }
    }

    pub fn perform( &mut self ) -> SplitIterator< 'a, D >
    {
      if !self.formed
      {
        self.formed = true;
      }

      let opts = SplitOptions
      {
        src : self.src,
        delimeter : self.delimeter.clone(),
        preserving_empty : self.preserving_empty,
        preserving_delimeters : self.preserving_delimeters,
        stripping : self.stripping,
      };
      opts.split()
    }
  }

  ///
  /// Function to split a string.
  ///
  /// It produces former. To convert former into options and run algorithm of splitting call `form()`.
  ///
  /// # Sample
  /// ```
  ///   let iter = wstring_tools::string::split()
  ///   .src( "abc def" )
  ///   .delimeter( " " )
  ///   .perform();
  /// ```

  pub fn split< 'a >() -> SplitOptionsBuilder< 'a, &'a str >
  {
    SplitOptionsBuilder::new( < &str >::default() )
  }

  ///

  pub fn split_generic< 'a, D : Searcher + Default + Clone >() -> SplitOptionsBuilder< 'a, D >
  {
    SplitOptionsBuilder::new( <D>::default() )
  }

}

/// Owned namespace of the module.
pub mod own
{
  use super::internal as i;

  pub use i::Split;
  pub use i::SplitType;
  pub use i::SplitFastIterator;
  pub use i::SplitOptions;
  pub use i::SplitOptionsAdapter;
  pub use i::split;
  pub use i::split_generic;
}

pub use own::*;

/// Exposed namespace of the module.
pub mod exposed
{
  use super::internal as i;

  pub use i::SplitOptionsAdapter;
  pub use i::split;
  pub use i::split_generic;
}

/// Namespace of the module to include with `use module::*`.
pub mod prelude
{
  use super::internal as i;

  pub use i::SplitOptionsAdapter;
}
<|endoftext|>


<commit_before>use std::os::unix::process::CommandExt;
use std::process::Command;

const SYSTEMD_RUN: &'static str = "/usr/bin/systemd-run";
const FIREJAIL: &'static str = "/usr/bin/firejail";

const FIREFOX: &'static str = "/usr/bin/firefox";
const FIREFOX_PROFILE: &'static str = "/etc/firejail/firefox.profile";
const FIREFOX_MOZILLA_SFW_PROFILE: &'static str = "default-1473025815439";

const FIREFOX_MEMORY_HIGH: i32 = 4420;
const FIREFOX_MEMORY_MAX: i32 = 5200;

const KEIRA: &'static str = "/home/kalvin/Downloads/.firefox-nightly/firefox";
const KEIRA_PROFILE: &'static str = "/etc/firejail/firefox-nightly.profile";

const TERM: &'static str = "/usr/bin/alacritty";
const TERM_PROFILE: &'static str = "/etc/firejail/x-terminal-emulator.profile";

const THUNDERBIRD: &'static str = "/home/kalvin/Downloads/.thunderbird-beta/thunderbird";
const THUNDERBIRD_PROFILE: &'static str = "/etc/firejail/thunderbird-beta.profile";

const SWAYMSG: &'static str = "/usr/bin/swaymsg";
const SWAY_EXTERNAL_DISPLAY: &'static str = "HDMI-A-1";

#[derive(Debug)]
struct CgroupedFirejailedCommandOptions<'a> {
    bin_path: &'a str,
    memory_high: Option<i32>,
    memory_max: Option<i32>,
    firejail_profile: Option<&'a str>,
    implicit_extra_args: Option<&'a [&'a str]>,
    argv_remainder: Vec<String>,
}

fn cgrouped_firejail_command(options: CgroupedFirejailedCommandOptions) -> Command {
    let mut command = Command::new(SYSTEMD_RUN);
    command.args(&["--user", "--scope"]);
    command.args(&["-p", "MemorySwapMax=0"]);

    if let Some(memory_high) = options.memory_high {
        let memory_high_owned_string = format!("MemoryHigh={}M", memory_high);
        command.args(&["-p", memory_high_owned_string.as_str()]);
    }
    if let Some(memory_max) = options.memory_max {
        let memory_max_owned_string = format!("MemoryMax={}M", memory_max);
        command.args(&["-p", memory_max_owned_string.as_str()]);
    }

    if let Some(firejail_profile) = options.firejail_profile {
        command.arg(FIREJAIL);
        command.arg("--ignore=seccomp");
        command.arg(format!("--profile={}", firejail_profile));
    }

    command.arg(options.bin_path);

    if let Some(implicit_extra_args) = options.implicit_extra_args {
        command.args(implicit_extra_args);
    }

    command.args(options.argv_remainder);

    command
}

fn simple_firejail_command(target: &str, args: Vec<String>) -> Command {
    let mut command = Command::new(FIREJAIL);

    command.arg(format!("--profile=/etc/firejail/{}.profile", target));
    command.arg(format!("/usr/bin/{}", target));
    command.args(args);

    command
}

fn swaymsg_display_command(args: Vec<String>) -> Command {
    let mut command = Command::new(SWAYMSG);
    command.arg(format!(
        "output {} {}",
        SWAY_EXTERNAL_DISPLAY,
        match args[0].as_str() {
            "e" => "enable",
            "d" => "disable",
            _ => panic!(
                "bad argument for swaymsg ``output'' command: ``{}''",
                args[0].as_str()
            ),
        }
    ));

    command
}

fn init_command() -> Command {
    let mut args_less_the_first = std::env::args();
    // Pops the name of this executable.
    args_less_the_first.next();

    // Pops the name of the unner target.
    let target = args_less_the_first.next().unwrap();

    // Copies and collects the remaining argv.
    let args: Vec<String> = args_less_the_first.collect();

    match target.as_str() {
        "d" => return swaymsg_display_command(args),
        "ff" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: args,
            });
        }
        "keira" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: KEIRA,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(KEIRA_PROFILE),
                implicit_extra_args: Some(&["-P", "nightly"]),
                argv_remainder: args,
            });
        }
        "npv" => return simple_firejail_command("mpv", args),
        "read" => {
            let formatted_args: Vec<String> = vec![format!("about:reader?url={}", args[0])];
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: formatted_args,
            });
        }
        "t" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: TERM,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(TERM_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "tbb" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: THUNDERBIRD,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(THUNDERBIRD_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            });
        }
        "vlk" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: "/usr/bin/vlc",
                memory_high: None,
                memory_max: None,
                firejail_profile: Some("/etc/firejail/vlc.profile"),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "e" => return simple_firejail_command("evince", args),
        _ => panic!(format!("no handler for ``{}''", target)),
    }
}

fn main() {
    let mut command = init_command();
    println!("{:#?}", command);
    command.exec();
}
<commit_msg>unner: handle quodlibet commands<commit_after>use std::io::Write;
use std::os::unix::process::CommandExt;
use std::process::Command;

const SYSTEMD_RUN: &'static str = "/usr/bin/systemd-run";
const FIREJAIL: &'static str = "/usr/bin/firejail";

const FIREFOX: &'static str = "/usr/bin/firefox";
const FIREFOX_PROFILE: &'static str = "/etc/firejail/firefox.profile";
const FIREFOX_MOZILLA_SFW_PROFILE: &'static str = "default-1473025815439";

const FIREFOX_MEMORY_HIGH: i32 = 4420;
const FIREFOX_MEMORY_MAX: i32 = 5200;

const KEIRA: &'static str = "/home/kalvin/Downloads/.firefox-nightly/firefox";
const KEIRA_PROFILE: &'static str = "/etc/firejail/firefox-nightly.profile";

const TERM: &'static str = "/usr/bin/alacritty";
const TERM_PROFILE: &'static str = "/etc/firejail/x-terminal-emulator.profile";

const THUNDERBIRD: &'static str = "/home/kalvin/Downloads/.thunderbird-beta/thunderbird";
const THUNDERBIRD_PROFILE: &'static str = "/etc/firejail/thunderbird-beta.profile";

const QUODLIBET_FIFO: &'static str = "/home/kalvin/.config/quodlibet/control";

const SWAYMSG: &'static str = "/usr/bin/swaymsg";
const SWAY_EXTERNAL_DISPLAY: &'static str = "HDMI-A-1";

#[derive(Debug)]
struct CgroupedFirejailedCommandOptions<'a> {
    bin_path: &'a str,
    memory_high: Option<i32>,
    memory_max: Option<i32>,
    firejail_profile: Option<&'a str>,
    implicit_extra_args: Option<&'a [&'a str]>,
    argv_remainder: Vec<String>,
}

fn cgrouped_firejail_command(options: CgroupedFirejailedCommandOptions) -> Command {
    let mut command = Command::new(SYSTEMD_RUN);
    command.args(&["--user", "--scope"]);
    command.args(&["-p", "MemorySwapMax=0"]);

    if let Some(memory_high) = options.memory_high {
        let memory_high_owned_string = format!("MemoryHigh={}M", memory_high);
        command.args(&["-p", memory_high_owned_string.as_str()]);
    }
    if let Some(memory_max) = options.memory_max {
        let memory_max_owned_string = format!("MemoryMax={}M", memory_max);
        command.args(&["-p", memory_max_owned_string.as_str()]);
    }

    if let Some(firejail_profile) = options.firejail_profile {
        command.arg(FIREJAIL);
        command.arg("--ignore=seccomp");
        command.arg(format!("--profile={}", firejail_profile));
    }

    command.arg(options.bin_path);

    if let Some(implicit_extra_args) = options.implicit_extra_args {
        command.args(implicit_extra_args);
    }

    command.args(options.argv_remainder);

    command
}

fn simple_firejail_command(target: &str, args: Vec<String>) -> Command {
    let mut command = Command::new(FIREJAIL);

    command.arg(format!("--profile=/etc/firejail/{}.profile", target));
    command.arg(format!("/usr/bin/{}", target));
    command.args(args);

    command
}

fn swaymsg_display_command(args: Vec<String>) -> Command {
    let mut command = Command::new(SWAYMSG);
    command.arg(format!(
        "output {} {}",
        SWAY_EXTERNAL_DISPLAY,
        match args[0].as_str() {
            "e" => "enable",
            "d" => "disable",
            _ => panic!(
                "bad argument for swaymsg ``output'' command: ``{}''",
                args[0].as_str()
            ),
        }
    ));

    command
}

fn quodlibet_command(args: Vec<String>) -> Command {
    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(QUODLIBET_FIFO)
        .unwrap();
    file.write_all(match args[0].as_str() {
        "pp" => "play-pause".as_bytes(),
        "sa" => "stop-after 1".as_bytes(),
        _ => panic!(
            "bad argument for quodlibet command: ``{}''",
            args[0].as_str()
        ),
    })
    .unwrap();

    Command::new("/bin/true")
}

fn init_command() -> Command {
    let mut args_less_the_first = std::env::args();
    // Pops the name of this executable.
    args_less_the_first.next();

    // Pops the name of the unner target.
    let target = args_less_the_first.next().unwrap();

    // Copies and collects the remaining argv.
    let args: Vec<String> = args_less_the_first.collect();

    match target.as_str() {
        "d" => return swaymsg_display_command(args),
        "ff" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: args,
            });
        }
        "keira" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: KEIRA,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(KEIRA_PROFILE),
                implicit_extra_args: Some(&["-P", "nightly"]),
                argv_remainder: args,
            });
        }
        "npv" => return simple_firejail_command("mpv", args),
        "q" => return quodlibet_command(args),
        "read" => {
            let formatted_args: Vec<String> = vec![format!("about:reader?url={}", args[0])];
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: formatted_args,
            });
        }
        "t" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: TERM,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(TERM_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "tbb" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: THUNDERBIRD,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(THUNDERBIRD_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            });
        }
        "vlk" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: "/usr/bin/vlc",
                memory_high: None,
                memory_max: None,
                firejail_profile: Some("/etc/firejail/vlc.profile"),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "e" => return simple_firejail_command("evince", args),
        _ => panic!(format!("no handler for ``{}''", target)),
    }
}

fn main() {
    let mut command = init_command();
    println!("{:#?}", command);
    command.exec();
}
<|endoftext|>


<commit_before>use habitat_core as hab_core;
use habitat_http_client as hab_http;

#[macro_use]
extern crate log;

#[macro_use]
extern crate serde_derive;
#[allow(unused_imports)]
#[macro_use]
extern crate serde_json;

pub mod artifactory;
pub mod builder;
pub mod error;
pub mod response;

use std::str::FromStr;

extern crate regex;
use regex::Regex;

use std::{fmt,
          io::Write,
          path::{Path,
                 PathBuf}};

use chrono::DateTime;
use reqwest::IntoUrl;

pub use crate::error::{Error,
                       Result};

use crate::{artifactory::ArtifactoryClient,
            builder::BuilderAPIClient,
            hab_core::{crypto::keys::box_key_pair::WrappedSealedBox,
                       env,
                       package::{PackageArchive,
                                 PackageIdent,
                                 PackageTarget},
                       ChannelIdent}};

pub trait DisplayProgress: Write + Send {
    fn size(&mut self, size: u64);
    fn finish(&mut self);
}

#[derive(Clone, Deserialize)]
#[serde(rename = "error")]
pub struct NetError {
    pub code: i32,
    pub msg:  String,
}

impl fmt::Display for NetError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[err: {:?}, msg: {}]", self.code, self.msg)
    }
}

#[derive(Clone, Default, Deserialize)]
pub struct Project {
    pub name:   String,
    pub ident:  String,
    pub state:  String,
    pub job_id: String,
    pub target: String,
}

impl fmt::Display for Project {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = format!("{:50} {}", self.ident, self.state);

        if let Ok(j) = self.job_id.parse::<i64>() {
            if j > 0 {
                let job_ids = format!(" (Job ID {})", self.job_id);
                s = s + &job_ids;
            }
        }

        write!(f, "{}", s)
    }
}

#[derive(Default, Deserialize)]
pub struct SchedulerResponse {
    pub id:           String,
    pub state:        String,
    pub projects:     Vec<Project>,
    pub created_at:   String,
    pub project_name: String,
    pub target:       String,
}

impl fmt::Display for SchedulerResponse {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut output = Vec::new();
        output.push(format!("Status for Job Group {} ({}): {}",
                            self.id, self.project_name, self.state));

        if let Ok(c) = DateTime::parse_from_rfc3339(&self.created_at) {
            output.push(format!("Created at: {}", c.to_string()));
        }

        if !self.projects.is_empty() {
            output.push("".to_string());
            output.push("Reverse dependencies:".to_string());
            let mut projects = self.projects.clone();
            projects.sort_by(|a, b| a.ident.cmp(&b.ident));

            for project in projects.iter() {
                output.push(project.to_string())
            }
        }

        write!(f, "{}", output.join("\n"))
    }
}

/// Custom conversion logic to allow `serde` to successfully
/// round-trip `u64` datatypes through JSON serialization.
///
/// To use it, add `#[serde(with = "json_u64")]` to any `u64`-typed struct
/// fields.
mod json_u64 {
    use serde::{self,
                Deserialize,
                Deserializer,
                Serializer};

    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn serialize<S>(num: &u64, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        let s = format!("{}", num);
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
        where D: Deserializer<'de>
    {
        let s = String::deserialize(deserializer)?;
        s.parse::<u64>().map_err(serde::de::Error::custom)
    }
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct OriginPrivateSigningKey {
    #[serde(with = "json_u64")]
    pub id: u64,
    #[serde(with = "json_u64")]
    pub origin_id: u64,
    pub name: String,
    pub revision: String,
    pub body: Vec<u8>,
    #[serde(with = "json_u64")]
    pub owner_id: u64,
}

mod json {
    #[derive(Clone, Deserialize)]
    pub struct Package {
        pub ident:    PackageIdent,
        pub checksum: String,
        pub manifest: String,
        pub deps:     Vec<PackageIdent>,
        pub tdeps:    Vec<PackageIdent>,
        pub exposes:  Vec<u32>,
        pub config:   String,
    }

    #[derive(Clone, Deserialize)]
    pub struct PackageIdent {
        pub origin:  String,
        pub name:    String,
        pub version: String,
        pub release: String,
    }

    impl From<PackageIdent> for super::PackageIdent {
        fn from(ident: PackageIdent) -> Self {
            super::PackageIdent { origin:  ident.origin,
                                  name:    ident.name,
                                  version: Some(ident.version),
                                  release: Some(ident.release), }
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct PackageResults<T> {
    pub range_start: isize,
    pub range_end:   isize,
    pub total_count: isize,
    pub data:        Vec<T>,
}

#[derive(Clone, Deserialize)]
pub struct OriginChannelIdent {
    pub name: String,
}

#[derive(Clone, Deserialize)]
pub struct OriginSecret {
    pub id:        String,
    pub origin_id: String,
    pub name:      String,
    pub value:     String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct OriginKeyIdent {
    pub origin:   String,
    pub revision: String,
    pub location: String,
}

// Expected format: "origin-revision.extension"
impl FromStr for OriginKeyIdent {
    type Err = Error;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let re = Regex::new(r"([\w]+)([-])([\d]+)").unwrap();

        let caps = match re.captures(s) {
            Some(caps) => caps,
            None => return Err(Error::NotSupported),
        };

        Ok(OriginKeyIdent { origin:   caps.get(1).unwrap().as_str().to_string(),
                            revision: caps.get(3).unwrap().as_str().to_string(),
                            location: "".to_string(), })
    }
}

#[derive(Deserialize)]
pub struct ReverseDependencies {
    pub origin: String,
    pub name:   String,
    pub rdeps:  Vec<String>,
}

#[derive(Clone,Copy,Debug)]
pub enum BuildOnUpload {
    PackageDefault,
    Disable,
}

pub trait BuilderAPIProvider: Sync + Send {
    type Progress;

    fn get_origin_schedule(&self, origin: &str, limit: usize) -> Result<Vec<SchedulerResponse>>;

    fn get_schedule(&self, group_id: i64, include_projects: bool) -> Result<SchedulerResponse>;

    fn schedule_job(&self,
                    ident_and_target: (&PackageIdent, PackageTarget),
                    package_only: bool,
                    token: &str)
                    -> Result<(String)>;

    fn fetch_rdeps(&self, ident_and_target: (&PackageIdent, PackageTarget)) -> Result<Vec<String>>;

    fn job_group_promote_or_demote(&self,
                                   group_id: u64,
                                   idents: &[String],
                                   channel: &ChannelIdent,
                                   token: &str,
                                   promote: bool)
                                   -> Result<()>;

    fn job_group_cancel(&self, group_id: u64, token: &str) -> Result<()>;

    fn fetch_origin_public_encryption_key(&self,
                                          origin: &str,
                                          token: &str,
                                          dst_path: &Path,
                                          progress: Option<Self::Progress>)
                                          -> Result<PathBuf>;

    fn create_origin(&self, origin: &str, token: &str) -> Result<()>;

    fn create_origin_secret(&self,
                            origin: &str,
                            token: &str,
                            key: &str,
                            secret: &WrappedSealedBox)
                            -> Result<()>;

    fn delete_origin_secret(&self, origin: &str, token: &str, key: &str) -> Result<()>;

    fn delete_origin(&self, origin: &str, token: &str) -> Result<()>;

    fn list_origin_secrets(&self, origin: &str, token: &str) -> Result<Vec<String>>;

    fn put_package(&self,
                   pa: &mut PackageArchive,
                   token: &str,
                   force_upload: bool,
                   auto_build: BuildOnUpload,
                   progress: Option<Self::Progress>)
                   -> Result<()>;

    fn x_put_package(&self, pa: &mut PackageArchive, token: &str) -> Result<()>;

    fn fetch_package(&self,
                     ident_and_target: (&PackageIdent, PackageTarget),
                     token: Option<&str>,
                     dst_path: &Path,
                     progress: Option<Self::Progress>)
                     -> Result<PackageArchive>;

    fn check_package(&self,
                     ident_and_target: (&PackageIdent, PackageTarget),
                     token: Option<&str>)
                     -> Result<()>;

    fn show_package(&self,
                    ident_and_target: (&PackageIdent, PackageTarget),
                    channel: &ChannelIdent,
                    token: Option<&str>)
                    -> Result<PackageIdent>;

    fn delete_package(&self,
                      ident_and_target: (&PackageIdent, PackageTarget),
                      token: &str)
                      -> Result<()>;

    fn search_package(&self,
                      search_term: &str,
                      limit: usize,
                      token: Option<&str>)
                      -> Result<(Vec<PackageIdent>, usize)>;

    fn create_channel(&self, origin: &str, channel: &ChannelIdent, token: &str) -> Result<()>;

    fn delete_channel(&self, origin: &str, channel: &ChannelIdent, token: &str) -> Result<()>;

    fn list_channels(&self, origin: &str, include_sandbox_channels: bool) -> Result<Vec<String>>;

    fn promote_package(&self,
                       ident_and_target: (&PackageIdent, PackageTarget),
                       channel: &ChannelIdent,
                       token: &str)
                       -> Result<()>;

    fn demote_package(&self,
                      ident_and_target: (&PackageIdent, PackageTarget),
                      channel: &ChannelIdent,
                      token: &str)
                      -> Result<()>;

    fn put_origin_key(&self,
                      origin: &str,
                      revision: &str,
                      src_path: &Path,
                      token: &str,
                      progress: Option<Self::Progress>)
                      -> Result<()>;

    fn fetch_origin_key(&self,
                        origin: &str,
                        revision: &str,
                        token: Option<&str>,
                        dst_path: &Path,
                        progress: Option<Self::Progress>)
                        -> Result<PathBuf>;

    fn put_origin_secret_key(&self,
                             origin: &str,
                             revision: &str,
                             src_path: &Path,
                             token: &str,
                             progress: Option<Self::Progress>)
                             -> Result<()>;

    fn fetch_secret_origin_key(&self,
                               origin: &str,
                               token: &str,
                               dst_path: &Path,
                               progress: Option<Self::Progress>)
                               -> Result<PathBuf>;

    fn show_origin_keys(&self, origin: &str) -> Result<Vec<OriginKeyIdent>>;

    fn package_channels(&self,
                        ident_and_target: (&PackageIdent, PackageTarget),
                        token: Option<&str>)
                        -> Result<Vec<String>>;
}

pub struct Client;
pub type BoxedClient = Box<dyn BuilderAPIProvider<Progress = Box<dyn DisplayProgress>>>;

impl Client {
    #[allow(clippy::new_ret_no_self)]
    pub fn new<U>(endpoint: U,
                  product: &str,
                  version: &str,
                  fs_root_path: Option<&Path>)
                  -> Result<BoxedClient>
        where U: IntoUrl
    {
        let endpoint = endpoint.into_url().map_err(Error::ReqwestError)?;

        match &env::var("HAB_BLDR_PROVIDER").unwrap_or_else(|_| "builder".to_string())[..] {
            "artifactory" => ArtifactoryClient::create(endpoint, product, version, fs_root_path),
            _ => BuilderAPIClient::create(endpoint, product, version, fs_root_path),
        }
    }
}

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

    #[test]
    fn json_round_trip_u64_fields() {
        let pre = OriginPrivateSigningKey { id:        705_705_315_793_903_646,
                                            origin_id: 705_705_305_031_319_582,
                                            name:      "core".to_string(),
                                            revision:  "20160810182414".to_string(),
                                            body:      vec![1, 2, 3],
                                            owner_id:  0, };

        let as_json = serde_json::to_value(&pre).unwrap();
        let expected = json!({
            "id": "705705315793903646",
            "origin_id": "705705305031319582",
            "name": "core",
            "revision": "20160810182414",
            "body": [
                1,
                2,
                3
            ],
            "owner_id": "0"
        });
        assert_eq!(as_json, expected);

        let post: OriginPrivateSigningKey = serde_json::from_value(as_json).unwrap();
        assert_eq!(pre.id, post.id);
        assert_eq!(pre.origin_id, post.origin_id);
        assert_eq!(pre.owner_id, post.owner_id);
    }
}
<commit_msg>Rustfmt<commit_after>use habitat_core as hab_core;
use habitat_http_client as hab_http;

#[macro_use]
extern crate log;

#[macro_use]
extern crate serde_derive;
#[allow(unused_imports)]
#[macro_use]
extern crate serde_json;

pub mod artifactory;
pub mod builder;
pub mod error;
pub mod response;

use std::str::FromStr;

extern crate regex;
use regex::Regex;

use std::{fmt,
          io::Write,
          path::{Path,
                 PathBuf}};

use chrono::DateTime;
use reqwest::IntoUrl;

pub use crate::error::{Error,
                       Result};

use crate::{artifactory::ArtifactoryClient,
            builder::BuilderAPIClient,
            hab_core::{crypto::keys::box_key_pair::WrappedSealedBox,
                       env,
                       package::{PackageArchive,
                                 PackageIdent,
                                 PackageTarget},
                       ChannelIdent}};

pub trait DisplayProgress: Write + Send {
    fn size(&mut self, size: u64);
    fn finish(&mut self);
}

#[derive(Clone, Deserialize)]
#[serde(rename = "error")]
pub struct NetError {
    pub code: i32,
    pub msg:  String,
}

impl fmt::Display for NetError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[err: {:?}, msg: {}]", self.code, self.msg)
    }
}

#[derive(Clone, Default, Deserialize)]
pub struct Project {
    pub name:   String,
    pub ident:  String,
    pub state:  String,
    pub job_id: String,
    pub target: String,
}

impl fmt::Display for Project {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = format!("{:50} {}", self.ident, self.state);

        if let Ok(j) = self.job_id.parse::<i64>() {
            if j > 0 {
                let job_ids = format!(" (Job ID {})", self.job_id);
                s = s + &job_ids;
            }
        }

        write!(f, "{}", s)
    }
}

#[derive(Default, Deserialize)]
pub struct SchedulerResponse {
    pub id:           String,
    pub state:        String,
    pub projects:     Vec<Project>,
    pub created_at:   String,
    pub project_name: String,
    pub target:       String,
}

impl fmt::Display for SchedulerResponse {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut output = Vec::new();
        output.push(format!("Status for Job Group {} ({}): {}",
                            self.id, self.project_name, self.state));

        if let Ok(c) = DateTime::parse_from_rfc3339(&self.created_at) {
            output.push(format!("Created at: {}", c.to_string()));
        }

        if !self.projects.is_empty() {
            output.push("".to_string());
            output.push("Reverse dependencies:".to_string());
            let mut projects = self.projects.clone();
            projects.sort_by(|a, b| a.ident.cmp(&b.ident));

            for project in projects.iter() {
                output.push(project.to_string())
            }
        }

        write!(f, "{}", output.join("\n"))
    }
}

/// Custom conversion logic to allow `serde` to successfully
/// round-trip `u64` datatypes through JSON serialization.
///
/// To use it, add `#[serde(with = "json_u64")]` to any `u64`-typed struct
/// fields.
mod json_u64 {
    use serde::{self,
                Deserialize,
                Deserializer,
                Serializer};

    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn serialize<S>(num: &u64, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        let s = format!("{}", num);
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
        where D: Deserializer<'de>
    {
        let s = String::deserialize(deserializer)?;
        s.parse::<u64>().map_err(serde::de::Error::custom)
    }
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct OriginPrivateSigningKey {
    #[serde(with = "json_u64")]
    pub id: u64,
    #[serde(with = "json_u64")]
    pub origin_id: u64,
    pub name: String,
    pub revision: String,
    pub body: Vec<u8>,
    #[serde(with = "json_u64")]
    pub owner_id: u64,
}

mod json {
    #[derive(Clone, Deserialize)]
    pub struct Package {
        pub ident:    PackageIdent,
        pub checksum: String,
        pub manifest: String,
        pub deps:     Vec<PackageIdent>,
        pub tdeps:    Vec<PackageIdent>,
        pub exposes:  Vec<u32>,
        pub config:   String,
    }

    #[derive(Clone, Deserialize)]
    pub struct PackageIdent {
        pub origin:  String,
        pub name:    String,
        pub version: String,
        pub release: String,
    }

    impl From<PackageIdent> for super::PackageIdent {
        fn from(ident: PackageIdent) -> Self {
            super::PackageIdent { origin:  ident.origin,
                                  name:    ident.name,
                                  version: Some(ident.version),
                                  release: Some(ident.release), }
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct PackageResults<T> {
    pub range_start: isize,
    pub range_end:   isize,
    pub total_count: isize,
    pub data:        Vec<T>,
}

#[derive(Clone, Deserialize)]
pub struct OriginChannelIdent {
    pub name: String,
}

#[derive(Clone, Deserialize)]
pub struct OriginSecret {
    pub id:        String,
    pub origin_id: String,
    pub name:      String,
    pub value:     String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct OriginKeyIdent {
    pub origin:   String,
    pub revision: String,
    pub location: String,
}

// Expected format: "origin-revision.extension"
impl FromStr for OriginKeyIdent {
    type Err = Error;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let re = Regex::new(r"([\w]+)([-])([\d]+)").unwrap();

        let caps = match re.captures(s) {
            Some(caps) => caps,
            None => return Err(Error::NotSupported),
        };

        Ok(OriginKeyIdent { origin:   caps.get(1).unwrap().as_str().to_string(),
                            revision: caps.get(3).unwrap().as_str().to_string(),
                            location: "".to_string(), })
    }
}

#[derive(Deserialize)]
pub struct ReverseDependencies {
    pub origin: String,
    pub name:   String,
    pub rdeps:  Vec<String>,
}

#[derive(Clone, Copy, Debug)]
pub enum BuildOnUpload {
    PackageDefault,
    Disable,
}

pub trait BuilderAPIProvider: Sync + Send {
    type Progress;

    fn get_origin_schedule(&self, origin: &str, limit: usize) -> Result<Vec<SchedulerResponse>>;

    fn get_schedule(&self, group_id: i64, include_projects: bool) -> Result<SchedulerResponse>;

    fn schedule_job(&self,
                    ident_and_target: (&PackageIdent, PackageTarget),
                    package_only: bool,
                    token: &str)
                    -> Result<(String)>;

    fn fetch_rdeps(&self, ident_and_target: (&PackageIdent, PackageTarget)) -> Result<Vec<String>>;

    fn job_group_promote_or_demote(&self,
                                   group_id: u64,
                                   idents: &[String],
                                   channel: &ChannelIdent,
                                   token: &str,
                                   promote: bool)
                                   -> Result<()>;

    fn job_group_cancel(&self, group_id: u64, token: &str) -> Result<()>;

    fn fetch_origin_public_encryption_key(&self,
                                          origin: &str,
                                          token: &str,
                                          dst_path: &Path,
                                          progress: Option<Self::Progress>)
                                          -> Result<PathBuf>;

    fn create_origin(&self, origin: &str, token: &str) -> Result<()>;

    fn create_origin_secret(&self,
                            origin: &str,
                            token: &str,
                            key: &str,
                            secret: &WrappedSealedBox)
                            -> Result<()>;

    fn delete_origin_secret(&self, origin: &str, token: &str, key: &str) -> Result<()>;

    fn delete_origin(&self, origin: &str, token: &str) -> Result<()>;

    fn list_origin_secrets(&self, origin: &str, token: &str) -> Result<Vec<String>>;

    fn put_package(&self,
                   pa: &mut PackageArchive,
                   token: &str,
                   force_upload: bool,
                   auto_build: BuildOnUpload,
                   progress: Option<Self::Progress>)
                   -> Result<()>;

    fn x_put_package(&self, pa: &mut PackageArchive, token: &str) -> Result<()>;

    fn fetch_package(&self,
                     ident_and_target: (&PackageIdent, PackageTarget),
                     token: Option<&str>,
                     dst_path: &Path,
                     progress: Option<Self::Progress>)
                     -> Result<PackageArchive>;

    fn check_package(&self,
                     ident_and_target: (&PackageIdent, PackageTarget),
                     token: Option<&str>)
                     -> Result<()>;

    fn show_package(&self,
                    ident_and_target: (&PackageIdent, PackageTarget),
                    channel: &ChannelIdent,
                    token: Option<&str>)
                    -> Result<PackageIdent>;

    fn delete_package(&self,
                      ident_and_target: (&PackageIdent, PackageTarget),
                      token: &str)
                      -> Result<()>;

    fn search_package(&self,
                      search_term: &str,
                      limit: usize,
                      token: Option<&str>)
                      -> Result<(Vec<PackageIdent>, usize)>;

    fn create_channel(&self, origin: &str, channel: &ChannelIdent, token: &str) -> Result<()>;

    fn delete_channel(&self, origin: &str, channel: &ChannelIdent, token: &str) -> Result<()>;

    fn list_channels(&self, origin: &str, include_sandbox_channels: bool) -> Result<Vec<String>>;

    fn promote_package(&self,
                       ident_and_target: (&PackageIdent, PackageTarget),
                       channel: &ChannelIdent,
                       token: &str)
                       -> Result<()>;

    fn demote_package(&self,
                      ident_and_target: (&PackageIdent, PackageTarget),
                      channel: &ChannelIdent,
                      token: &str)
                      -> Result<()>;

    fn put_origin_key(&self,
                      origin: &str,
                      revision: &str,
                      src_path: &Path,
                      token: &str,
                      progress: Option<Self::Progress>)
                      -> Result<()>;

    fn fetch_origin_key(&self,
                        origin: &str,
                        revision: &str,
                        token: Option<&str>,
                        dst_path: &Path,
                        progress: Option<Self::Progress>)
                        -> Result<PathBuf>;

    fn put_origin_secret_key(&self,
                             origin: &str,
                             revision: &str,
                             src_path: &Path,
                             token: &str,
                             progress: Option<Self::Progress>)
                             -> Result<()>;

    fn fetch_secret_origin_key(&self,
                               origin: &str,
                               token: &str,
                               dst_path: &Path,
                               progress: Option<Self::Progress>)
                               -> Result<PathBuf>;

    fn show_origin_keys(&self, origin: &str) -> Result<Vec<OriginKeyIdent>>;

    fn package_channels(&self,
                        ident_and_target: (&PackageIdent, PackageTarget),
                        token: Option<&str>)
                        -> Result<Vec<String>>;
}

pub struct Client;
pub type BoxedClient = Box<dyn BuilderAPIProvider<Progress = Box<dyn DisplayProgress>>>;

impl Client {
    #[allow(clippy::new_ret_no_self)]
    pub fn new<U>(endpoint: U,
                  product: &str,
                  version: &str,
                  fs_root_path: Option<&Path>)
                  -> Result<BoxedClient>
        where U: IntoUrl
    {
        let endpoint = endpoint.into_url().map_err(Error::ReqwestError)?;

        match &env::var("HAB_BLDR_PROVIDER").unwrap_or_else(|_| "builder".to_string())[..] {
            "artifactory" => ArtifactoryClient::create(endpoint, product, version, fs_root_path),
            _ => BuilderAPIClient::create(endpoint, product, version, fs_root_path),
        }
    }
}

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

    #[test]
    fn json_round_trip_u64_fields() {
        let pre = OriginPrivateSigningKey { id:        705_705_315_793_903_646,
                                            origin_id: 705_705_305_031_319_582,
                                            name:      "core".to_string(),
                                            revision:  "20160810182414".to_string(),
                                            body:      vec![1, 2, 3],
                                            owner_id:  0, };

        let as_json = serde_json::to_value(&pre).unwrap();
        let expected = json!({
            "id": "705705315793903646",
            "origin_id": "705705305031319582",
            "name": "core",
            "revision": "20160810182414",
            "body": [
                1,
                2,
                3
            ],
            "owner_id": "0"
        });
        assert_eq!(as_json, expected);

        let post: OriginPrivateSigningKey = serde_json::from_value(as_json).unwrap();
        assert_eq!(pre.id, post.id);
        assert_eq!(pre.origin_id, post.origin_id);
        assert_eq!(pre.owner_id, post.owner_id);
    }
}
<|endoftext|>