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

extern crate crypto;
mod pe;

use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::error::Error;
use std::mem;
use std::slice;
use std::env;

use crypto::{buffer, aes, blockmodes};

#[allow(unused_imports)]
use crypto::symmetriccipher::Encryptor;

/// Entry-point.
fn main() {
    println!("HotS-unpack unpacker by athre0z (c) 2015 zyantific.com\n");

    // Parse arguments.
    let args: Vec<_> = env::args().collect();
    if args.len() != 2 {
        println!("[*] Usage: {} <input binary>", args[0]);
        return;
    }

    // Read file.
    let mut f = match File::open(&Path::new(&args[1])) {
        Err(why) => panic!("[-] Couldn't open input file: {}", Error::description(&why)),
        Ok(file) => file,
    };

    let mut data = Vec::new();
    let data_size = match f.read_to_end(&mut data) {
        Err(why) => panic!("[-] Couldn't read input file: {}", Error::description(&why)),
        Ok(x) => x
    };

    println!("[+] Read file: {} bytes", data_size);

    // Parse DOS-header.
    let mz = get_data::<pe::ImageDosHeader>(&mut data, 0);
    if unsafe { (*mz).magic } != pe::DOS_MAGIC {
        panic!("[-] Invalid input file: invalid DOS header");
    }

    // Parse NT-header.
    let nt_header_offs = unsafe { (*mz).lfanew } as usize;
    let nt = get_data::<pe::ImageNtHeaders32>(&mut data, nt_header_offs);
    if unsafe { (*nt).signature } != pe::PE_MAGIC {
        panic!("[-] Invalid input file: invalid NT header");
    }

    // Obtain useful information.
    let sec_table_offs = nt_header_offs + mem::size_of::<pe::ImageNtHeaders32>();
    let num_secs = unsafe { (*nt).file_header.number_of_sections } as usize;
    let data_offs = sec_table_offs
        + mem::size_of::<pe::ImageSectionHeader>() * num_secs;
    let image_base = unsafe { (*nt).opt_header.image_base } as usize;

    // Scan for chunk table.
    println!("[*] Heuristically locating chunk table ...");
    let mut chunk_table_offs = 0;
    let mut chunk_table_len = 0;

    'scan: for i in data_offs..data.len() {
        let mut offs = 0usize;
        let mut last_addr = 0u32;

        // Check if candidate looks like a chunk table.
        while i + offs + mem::size_of::<ChunkEntry>() <= data.len() {
            unsafe {
                let cur = get_data::<ChunkEntry>(&mut data, i + offs);

                // The chunk table's end is indicated with an 0xFFFFFFFF.
                if offs >= 1000 * mem::size_of::<ChunkEntry>() && (*cur).offset == 0xFFFFFFFF {
                    chunk_table_offs = i;
                    chunk_table_len = (offs / mem::size_of::<ChunkEntry>()) - 1;
                    break 'scan;
                }

                // The chunk table entries are stored ascending.
                if (*cur).offset <= last_addr {
                    break;
                }

                last_addr = (*cur).offset;
                offs += mem::size_of::<ChunkEntry>();
            }
        }
    }

    if chunk_table_offs == 0 || chunk_table_len == 0 {
        panic!("[-] Unable to locate chunk table.");
    }

    println!("[+] Found chunk table @ FO 0x{:x} ({} entries).",
        chunk_table_offs, chunk_table_len);

    // Scan for AES key.
    println!("[*] Locating AES key ...");

    // 0xFF -> wildcard
    let pattern = [
        0x68, 0x80, 0x00, 0x00, 0x00, // push 80h
        0x68, 0xFF, 0xFF, 0xFF, 0xFF, // push offset g_aesInputKey
        0x68, 0xFF, 0xFF, 0xFF, 0xFF, // push offset g_aesKey
        0xE8, 0xFF, 0xFF, 0xFF, 0xFF, // call AesSetDecryptKey
        0xE9,                         // jmp  XXX
    ];

    let mut match_ = None;
    for offs in sec_table_offs..(data.len() - pattern.len()) {
        let mut exhausted = true;
        for (i, cur_pat_byte) in pattern.iter().enumerate() {
            if *cur_pat_byte == 0xFF { continue; }
            if data[offs + i] != *cur_pat_byte {
                exhausted = false;
                break;
            }
        }

        if exhausted {
            match_ = Some(offs);
            break;
        }
    }

    let aes_key_rva = match match_ {
        Some(x) => unsafe { *get_data::<u32>(&mut data, x + 6) as usize - image_base },
        None => panic!("[-] Unable to locate AES key.")
    };

    print!("[+] Found AES key @ RVA 0x{:x}: ", aes_key_rva);

    // Dump AES key.
    let aes_key = unsafe { *get_data_rva::<[u8; 16]>(
        &mut data, sec_table_offs, num_secs, aes_key_rva) };

    for i in aes_key.iter() {
        print!("{:02x} ", i);
    }
    print!("\n");

    // Collect all encrypted chunks into one buffer (we got plenty of memory, don't we? :p)
    println!("[*] Merging encrypted chunks ...", );
    let mut encrypted_blob = Vec::<u8>::new();
    for i in 0..chunk_table_len {
        let chunk_info = get_data::<ChunkEntry>(
            &mut data, chunk_table_offs + mem::size_of::<ChunkEntry>() * i);
        unsafe {
            encrypted_blob.extend(get_slice_rva::<u8>(
                &mut data, sec_table_offs, num_secs, (*chunk_info).offset as usize,
                (*chunk_info).size as usize).to_vec());
        }
    }

    let padding_length = 16 - encrypted_blob.len() % 16;
    if padding_length > 0 {
        encrypted_blob.extend(vec![0; padding_length]);
    }

    // Decrypt blob.
    println!("[*] Decrypting ...");
    let mut decryptor = aes::ecb_decryptor(
        aes::KeySize::KeySize128,
        &aes_key,
        blockmodes::NoPadding);
    let mut decrypted_blob = vec![0u8; encrypted_blob.len()];

    {
        let mut read_buf = buffer::RefReadBuffer::new(&encrypted_blob);
        let mut write_buf = buffer::RefWriteBuffer::new(&mut decrypted_blob);
        match decryptor.decrypt(&mut read_buf, &mut write_buf, true) {
            Err(why) => panic!("[-] Unable to decrypt data: {:?}", why),
            Ok(result) => match result {
                buffer::BufferResult::BufferUnderflow => {},
                buffer::BufferResult::BufferOverflow => unreachable!(),
            }
        }
    }

    // Write data from blob back into our file buffer.
    println!("[*] Putting decrypted data where it belongs ...");
    let mut read_offset = 0usize;
    for i in 0..chunk_table_len {
        let chunk_info = get_data::<ChunkEntry>(
            &mut data, chunk_table_offs + mem::size_of::<ChunkEntry>() * i);
        unsafe {
            let chunk_size = (*chunk_info).size as usize;
            let mut dst = get_slice_rva::<u8>(&mut data, sec_table_offs, num_secs,
                (*chunk_info).offset as usize, chunk_size);
            let src = &decrypted_blob[read_offset..read_offset + chunk_size];

            for (cur_src_byte, cur_dst_byte) in src.iter().zip(dst.iter_mut()) {
                *cur_dst_byte = *cur_src_byte;
            }

            read_offset += chunk_size;
        }
    }

    // Blizz moved the IAT to the .reloc section, which isn't loaded by IDA
    // when loading a file without the "manual load" option, which results in
    // unresolved API calls. Rename it to make IDA map it into the IDB by default.
    println!("[*] Fixing section table ...");
    for i in 0..num_secs {
        let cur_sec = get_data::<pe::ImageSectionHeader>(
            &mut data, sec_table_offs + mem::size_of::<pe::ImageSectionHeader>() * i);

        unsafe {
            if (*cur_sec).name.iter().zip(".reloc".as_bytes().iter()).all(|(a, b)| a == b) {
                for (src, dst) in ".reloc_".as_bytes().iter().zip((*cur_sec).name.iter_mut()) {
                    *dst = *src;
                }
            }
        }
    }

    // Write output file.
    println!("[*] Writing output file ...");
    let mut out_f = match File::create(&Path::new("out.exe")) {
        Err(why) => panic!("[-] Couldn't open output file: {}", Error::description(&why)),
        Ok(file) => file,
    };

    match out_f.write_all(&data) {
        Err(why) => panic!("[-] Couldn't write to output file: {}", Error::description(&why)),
        Ok(..) => println!("[+] Wrote {:?} bytes (out.exe).", data.len()),
    }
}

#[repr(C)]
#[packed]
struct ChunkEntry {
    pub offset: u32,
    pub size: u32,
}

/// Obtains a ptr to a struct inside the input file by it's file offset.
fn get_data<T>(data: &mut Vec<u8>, file_offset: usize) -> *mut T {
    if mem::size_of::<T>() > data.len() - file_offset {
        panic!("[-] Invalid input file: file address exceeds file boundaries.");
    }
    unsafe { mem::transmute(data.as_ptr() as usize + file_offset) }
}

/// Obtains a ptr to a struct inside the input file by it's RVA.
fn get_data_rva<T>(data: &mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize) -> *mut T {

    match rva_to_fo(data, sec_table_offs, num_secs, rva) {
        Some(rva) => get_data::<T>(data, rva),
        None => panic!("[-] Invalid input file: Pointer to an invalid RVA encountered.")
    }
}

/// Obtains a mutable slice from the input file by it's RVA.
fn get_slice_rva<'a, T>(data: &'a mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize, len: usize) -> &'a mut [T] {

    let file_offset = match rva_to_fo(data, sec_table_offs, num_secs, rva) {
        Some(fo) => fo,
        None => panic!("[-] Invalid input file: Pointer to an invalid RVA encountered.")
    };

    if mem::size_of::<T>() * len > data.len() - file_offset {
        panic!("[-] Invalid input file: file address exceeds file boundaries.");
    }

    unsafe { slice::from_raw_parts_mut(mem::transmute(
        data.as_ptr() as usize + file_offset), len) }
}

/// Translates an RVA to a file offset.
fn rva_to_fo(data: &mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize) -> Option<usize> {

    for i in 0..num_secs {
        let cur_sec = get_data::<pe::ImageSectionHeader>(
            data, sec_table_offs + mem::size_of::<pe::ImageSectionHeader>() * i);

        unsafe {
            let sec_va = (*cur_sec).virtual_address as usize;
            if rva >= sec_va && rva <= sec_va + (*cur_sec).virtual_size as usize {
                return Some(rva - sec_va + (*cur_sec).pointer_to_raw_data as usize);
            }
        }
    }

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

extern crate crypto;
mod pe;

use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::error::Error;
use std::mem;
use std::slice;
use std::env;

use crypto::{buffer, aes, blockmodes};

#[allow(unused_imports)]
use crypto::symmetriccipher::Encryptor;

/// Entry-point.
fn main() {
    println!("HotS-unpack by athre0z (c) 2015 zyantific.com\n");

    // Parse arguments.
    let args: Vec<_> = env::args().collect();
    if args.len() != 2 {
        println!("[*] Usage: {} <input binary>", args[0]);
        return;
    }

    // Read file.
    let mut f = match File::open(&Path::new(&args[1])) {
        Err(why) => panic!("[-] Couldn't open input file: {}", Error::description(&why)),
        Ok(file) => file,
    };

    let mut data = Vec::new();
    let data_size = match f.read_to_end(&mut data) {
        Err(why) => panic!("[-] Couldn't read input file: {}", Error::description(&why)),
        Ok(x) => x
    };

    println!("[+] Read file: {} bytes", data_size);

    // Parse DOS-header.
    let mz = get_data::<pe::ImageDosHeader>(&mut data, 0);
    if unsafe { (*mz).magic } != pe::DOS_MAGIC {
        panic!("[-] Invalid input file: invalid DOS header");
    }

    // Parse NT-header.
    let nt_header_offs = unsafe { (*mz).lfanew } as usize;
    let nt = get_data::<pe::ImageNtHeaders32>(&mut data, nt_header_offs);
    if unsafe { (*nt).signature } != pe::PE_MAGIC {
        panic!("[-] Invalid input file: invalid NT header");
    }

    // Obtain useful information.
    let sec_table_offs = nt_header_offs + mem::size_of::<pe::ImageNtHeaders32>();
    let num_secs = unsafe { (*nt).file_header.number_of_sections } as usize;
    let data_offs = sec_table_offs
        + mem::size_of::<pe::ImageSectionHeader>() * num_secs;
    let image_base = unsafe { (*nt).opt_header.image_base } as usize;

    // Scan for chunk table.
    println!("[*] Heuristically locating chunk table ...");
    let mut chunk_table_offs = 0;
    let mut chunk_table_len = 0;

    'scan: for i in data_offs..data.len() {
        let mut offs = 0usize;
        let mut last_addr = 0u32;

        // Check if candidate looks like a chunk table.
        while i + offs + mem::size_of::<ChunkEntry>() <= data.len() {
            unsafe {
                let cur = get_data::<ChunkEntry>(&mut data, i + offs);

                // The chunk table's end is indicated with an 0xFFFFFFFF.
                if offs >= 1000 * mem::size_of::<ChunkEntry>() && (*cur).offset == 0xFFFFFFFF {
                    chunk_table_offs = i;
                    chunk_table_len = (offs / mem::size_of::<ChunkEntry>()) - 1;
                    break 'scan;
                }

                // The chunk table entries are stored ascending.
                if (*cur).offset <= last_addr {
                    break;
                }

                last_addr = (*cur).offset;
                offs += mem::size_of::<ChunkEntry>();
            }
        }
    }

    if chunk_table_offs == 0 || chunk_table_len == 0 {
        panic!("[-] Unable to locate chunk table.");
    }

    println!("[+] Found chunk table @ FO 0x{:x} ({} entries).",
        chunk_table_offs, chunk_table_len);

    // Scan for AES key.
    println!("[*] Locating AES key ...");

    // 0xFF -> wildcard
    let pattern = [
        0x68, 0x80, 0x00, 0x00, 0x00, // push 80h
        0x68, 0xFF, 0xFF, 0xFF, 0xFF, // push offset g_aesInputKey
        0x68, 0xFF, 0xFF, 0xFF, 0xFF, // push offset g_aesKey
        0xE8, 0xFF, 0xFF, 0xFF, 0xFF, // call AesSetDecryptKey
        0xE9,                         // jmp  XXX
    ];

    let mut match_ = None;
    for offs in sec_table_offs..(data.len() - pattern.len()) {
        let mut exhausted = true;
        for (i, cur_pat_byte) in pattern.iter().enumerate() {
            if *cur_pat_byte == 0xFF { continue; }
            if data[offs + i] != *cur_pat_byte {
                exhausted = false;
                break;
            }
        }

        if exhausted {
            match_ = Some(offs);
            break;
        }
    }

    let aes_key_rva = match match_ {
        Some(x) => unsafe { *get_data::<u32>(&mut data, x + 6) as usize - image_base },
        None => panic!("[-] Unable to locate AES key.")
    };

    print!("[+] Found AES key @ RVA 0x{:x}: ", aes_key_rva);

    // Dump AES key.
    let aes_key = unsafe { *get_data_rva::<[u8; 16]>(
        &mut data, sec_table_offs, num_secs, aes_key_rva) };

    for i in aes_key.iter() {
        print!("{:02x} ", i);
    }
    print!("\n");

    // Collect all encrypted chunks into one buffer (we got plenty of memory, don't we? :p)
    println!("[*] Merging encrypted chunks ...", );
    let mut encrypted_blob = Vec::<u8>::new();
    for i in 0..chunk_table_len {
        let chunk_info = get_data::<ChunkEntry>(
            &mut data, chunk_table_offs + mem::size_of::<ChunkEntry>() * i);
        unsafe {
            encrypted_blob.extend(get_slice_rva::<u8>(
                &mut data, sec_table_offs, num_secs, (*chunk_info).offset as usize,
                (*chunk_info).size as usize).to_vec());
        }
    }

    let padding_length = 16 - encrypted_blob.len() % 16;
    if padding_length > 0 {
        encrypted_blob.extend(vec![0; padding_length]);
    }

    // Decrypt blob.
    println!("[*] Decrypting ...");
    let mut decryptor = aes::ecb_decryptor(
        aes::KeySize::KeySize128,
        &aes_key,
        blockmodes::NoPadding);
    let mut decrypted_blob = vec![0u8; encrypted_blob.len()];

    {
        let mut read_buf = buffer::RefReadBuffer::new(&encrypted_blob);
        let mut write_buf = buffer::RefWriteBuffer::new(&mut decrypted_blob);
        match decryptor.decrypt(&mut read_buf, &mut write_buf, true) {
            Err(why) => panic!("[-] Unable to decrypt data: {:?}", why),
            Ok(result) => match result {
                buffer::BufferResult::BufferUnderflow => {},
                buffer::BufferResult::BufferOverflow => unreachable!(),
            }
        }
    }

    // Write data from blob back into our file buffer.
    println!("[*] Putting decrypted data where it belongs ...");
    let mut read_offset = 0usize;
    for i in 0..chunk_table_len {
        let chunk_info = get_data::<ChunkEntry>(
            &mut data, chunk_table_offs + mem::size_of::<ChunkEntry>() * i);
        unsafe {
            let chunk_size = (*chunk_info).size as usize;
            let mut dst = get_slice_rva::<u8>(&mut data, sec_table_offs, num_secs,
                (*chunk_info).offset as usize, chunk_size);
            let src = &decrypted_blob[read_offset..read_offset + chunk_size];

            for (cur_src_byte, cur_dst_byte) in src.iter().zip(dst.iter_mut()) {
                *cur_dst_byte = *cur_src_byte;
            }

            read_offset += chunk_size;
        }
    }

    // Blizz moved the IAT to the .reloc section, which isn't loaded by IDA
    // when loading a file without the "manual load" option, which results in
    // unresolved API calls. Rename it to make IDA map it into the IDB by default.
    println!("[*] Fixing section table ...");
    for i in 0..num_secs {
        let cur_sec = get_data::<pe::ImageSectionHeader>(
            &mut data, sec_table_offs + mem::size_of::<pe::ImageSectionHeader>() * i);

        unsafe {
            if (*cur_sec).name.iter().zip(".reloc".as_bytes().iter()).all(|(a, b)| a == b) {
                for (src, dst) in ".reloc_".as_bytes().iter().zip((*cur_sec).name.iter_mut()) {
                    *dst = *src;
                }
            }
        }
    }

    // Write output file.
    println!("[*] Writing output file ...");
    let mut out_f = match File::create(&Path::new("out.exe")) {
        Err(why) => panic!("[-] Couldn't open output file: {}", Error::description(&why)),
        Ok(file) => file,
    };

    match out_f.write_all(&data) {
        Err(why) => panic!("[-] Couldn't write to output file: {}", Error::description(&why)),
        Ok(..) => println!("[+] Wrote {:?} bytes (out.exe).", data.len()),
    }
}

#[repr(C)]
#[packed]
struct ChunkEntry {
    pub offset: u32,
    pub size: u32,
}

/// Obtains a ptr to a struct inside the input file by it's file offset.
fn get_data<T>(data: &mut Vec<u8>, file_offset: usize) -> *mut T {
    if mem::size_of::<T>() > data.len() - file_offset {
        panic!("[-] Invalid input file: file address exceeds file boundaries.");
    }
    unsafe { mem::transmute(data.as_ptr() as usize + file_offset) }
}

/// Obtains a ptr to a struct inside the input file by it's RVA.
fn get_data_rva<T>(data: &mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize) -> *mut T {

    match rva_to_fo(data, sec_table_offs, num_secs, rva) {
        Some(rva) => get_data::<T>(data, rva),
        None => panic!("[-] Invalid input file: Pointer to an invalid RVA encountered.")
    }
}

/// Obtains a mutable slice from the input file by it's RVA.
fn get_slice_rva<'a, T>(data: &'a mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize, len: usize) -> &'a mut [T] {

    let file_offset = match rva_to_fo(data, sec_table_offs, num_secs, rva) {
        Some(fo) => fo,
        None => panic!("[-] Invalid input file: Pointer to an invalid RVA encountered.")
    };

    if mem::size_of::<T>() * len > data.len() - file_offset {
        panic!("[-] Invalid input file: file address exceeds file boundaries.");
    }

    unsafe { slice::from_raw_parts_mut(mem::transmute(
        data.as_ptr() as usize + file_offset), len) }
}

/// Translates an RVA to a file offset.
fn rva_to_fo(data: &mut Vec<u8>, sec_table_offs: usize,
    num_secs: usize, rva: usize) -> Option<usize> {

    for i in 0..num_secs {
        let cur_sec = get_data::<pe::ImageSectionHeader>(
            data, sec_table_offs + mem::size_of::<pe::ImageSectionHeader>() * i);

        unsafe {
            let sec_va = (*cur_sec).virtual_address as usize;
            if rva >= sec_va && rva <= sec_va + (*cur_sec).virtual_size as usize {
                return Some(rva - sec_va + (*cur_sec).pointer_to_raw_data as usize);
            }
        }
    }

    None
}<|endoftext|>


<commit_before>/*
   Copyright 2017 Miguel Peláez <kernelfreeze@greenlab.games>
   Copyright 2017 Raúl Salas <raulsalas.martin@greenlab.games>

   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.
*/

#![deny(unused_imports)]

#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;
extern crate rand;
extern crate badlog;
extern crate allegro;
extern crate os_type;
extern crate dirs;

mod scenes;
mod client;

use rand::Rng;
use dirs::Directories;
use std::env::set_current_dir;

fn main() {
    badlog::init_from_env("LOG_LEVEL");

    let matches = clap_app!(litecraft =>
        (version: client::VERSION)
        (author: "Litecraft Team")
        (about: "Open source, clean room implementation of Minecraft Client")
        (@arg session: +required "Sets the user session ID")
        (@arg server: -s ... "Auto-join a server")
        (@arg path: -p ... "Litecraft home, must have all resources available")
    ).get_matches();

    // Epic hardcoded quotes!
    let hello = [
        // Litecrafty
        "Litecraft is love, Litecraft is life!",
        "Now available on bluray",
        "Very fast!",
        "Sky is the Limit (y = 255)",
        "Open Source!",
        "Less bugs!",

        // Nice...
        "Knowledge is having the right answer. Intelligence is asking the right question",
        "Wake me up when it's all over",
        "A person who never made a mistake never tried anything new",
        "There is nothing permanent except change",
        "If you cannot do great things, do small things in a great way",
        "The journey of a thousand miles begins with one step",

        // Random stuff
        "Citrate Caffeine 1 oz\nExtract Vanilla 1 oz\nFlavouring 2.5 oz",
        "Triskaidekaphobic, 13",
    ];

    println!(
        r"  _    _ _                    __ _
 | |  (_) |_ ___ __ _ _ __ _ / _| |_
 | |__| |  _/ -_) _| '_/ _` |  _|  _|
 |____|_|\__\___\__|_| \__,_|_|  \__|
                                     "
    );
    println!("{}\n", rand::thread_rng().choose(&hello).unwrap());

    let os = os_type::current_platform();
    println!(
        "{} for Minecraft Modern {}, running on {:?} {}",
        client::VERSION,
        client::MINECRAFT,
        os.os_type,
        os.version
    );
    info!("Starting engine...");
    let path;

    if let Some(i) = matches.value_of("path") {
        path = String::from(i);
    } else {
        let data = Directories::with_prefix("litecraft", "Litecraft").unwrap();
        path = String::from(data.config_home().to_str().unwrap());
    };

    info!("Using home {}", path);
    assert!(set_current_dir(&path).is_ok());

    client::run(matches.value_of("session").unwrap());
}
<commit_msg>Parameter change<commit_after>/*
   Copyright 2017 Miguel Peláez <kernelfreeze@greenlab.games>
   Copyright 2017 Raúl Salas <raulsalas.martin@greenlab.games>

   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.
*/

#![deny(unused_imports)]

#[macro_use]
extern crate log;
#[macro_use]
extern crate clap;
extern crate rand;
extern crate badlog;
extern crate allegro;
extern crate os_type;
extern crate dirs;

mod scenes;
mod client;

use rand::Rng;
use dirs::Directories;
use std::env::set_current_dir;

fn main() {
    badlog::init_from_env("LOG_LEVEL");

    let matches = clap_app!(litecraft =>
        (version: client::VERSION)
        (author: "Litecraft Team")
        (about: "Open source, clean room implementation of Minecraft Client")
        (@arg session: +required "Sets the user session ID")
        (@arg server: -s +takes_value "Auto-join a server")
        (@arg path: -p --path +takes_value "Litecraft home, must have all resources available")
    ).get_matches();

    // Epic hardcoded quotes!
    let hello = [
        // Litecrafty
        "Litecraft is love, Litecraft is life!",
        "Now available on bluray",
        "Very fast!",
        "Sky is the Limit (y = 255)",
        "Open Source!",
        "Less bugs!",

        // Nice...
        "Knowledge is having the right answer. Intelligence is asking the right question",
        "Wake me up when it's all over",
        "A person who never made a mistake never tried anything new",
        "There is nothing permanent except change",
        "If you cannot do great things, do small things in a great way",
        "The journey of a thousand miles begins with one step",

        // Random stuff
        "Citrate Caffeine 1 oz\nExtract Vanilla 1 oz\nFlavouring 2.5 oz",
        "Triskaidekaphobic, 13",
    ];

    println!(
        r"  _    _ _                    __ _
 | |  (_) |_ ___ __ _ _ __ _ / _| |_
 | |__| |  _/ -_) _| '_/ _` |  _|  _|
 |____|_|\__\___\__|_| \__,_|_|  \__|
                                     "
    );
    println!("{}\n", rand::thread_rng().choose(&hello).unwrap());

    let os = os_type::current_platform();
    println!(
        "{} for Minecraft Modern {}, running on {:?} {}",
        client::VERSION,
        client::MINECRAFT,
        os.os_type,
        os.version
    );
    info!("Starting engine...");
    let path;

    if let Some(i) = matches.value_of("path") {
        path = String::from(i);
    } else {
        let data = Directories::with_prefix("litecraft", "Litecraft").unwrap();
        path = String::from(data.config_home().to_str().unwrap());
    };

    info!("Using home {}", path);
    assert!(set_current_dir(&path).is_ok());

    client::run(matches.value_of("session").unwrap());
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::path::Path;
use std::process::{exit, Command};

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("Skeleton")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Skeleton project manager")
        .args_from_usage("-l, --lang=<LANG>  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap().to_string();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let config: Config = match parse_config(lang) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };

    if let Some(dirs) = config.mkdir {
        for dir in dirs {
            if !file_exists(&dir) {
                match create_dir(dir.clone()) {
                    Ok(_) => println!("Created directory '{}'", dir),
                    Err(e) => println!("Could not create directory '{}'. {:?}", dir, e),
                }
            } else {
                println!("Directory '{}' already exists", dir);
            }
        }
    }

    if let Some(gi) = config.gitignore {
        let ign = gitignore::get_gitignore(gi).unwrap();
        let file = OpenOptions::new()
            .create_new(true)
            .append(true)
            .open("gitignore");
        if let Ok(mut file) = file {
            writeln!(file, "{}", ign);
        } else {
            println!("Could not write .gitignore");
        }
    }

    if let Some(fnames) = config.touch {
        for file in fnames {
            if !create_file(&file) {
                println!("Could not create file '{}'", file);
            }
        }
    }

    if let Some(cmds) = config.exec {
        for cmd in cmds {
            println!("Executing '{}'", cmd);
            match exec(&cmd) {
                Ok((out, err)) => {
                    println!("stdout: {}", out);
                    println!("stderr: {}", err);
                }
                Err(e) => println!("Failed to execute: {:?}", e),
            }
        }
    }
}

fn file_exists(name: &String) -> bool {
    Path::new(&name).exists()
}

fn exec(cmd: &String) -> Result<(String, String)> {
    #[cfg(unix)]
    let shell = "sh";
    #[cfg(unix)]
    let arg1 = "-c";
    #[cfg(windows)]
    let shell = "cmd.exe";
    #[cfg(windows)]
    let arg1 = "/C";

    let out = match Command::new(shell).arg(arg1).arg(cmd).output() {
        Ok(o) => o,
        Err(e) => return Err(e),
    };

    let stdout = match String::from_utf8(out.stdout) {
        Ok(so) => so,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    let stderr = match String::from_utf8(out.stderr) {
        Ok(se) => se,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    Ok((stdout, stderr))
}

fn create_file(file_name: &String) -> bool {
    !file_exists(&file_name) &&
    match OpenOptions::new()
              .append(true)
              .create(true)
              .open(file_name) {
        Ok(_) => true,
        Err(_) => false,
    }
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}

#[test]
fn test_file_exists() {
    assert!(file_exists(&"test/foo.toml".to_string()));
    assert!(!file_exists(&"test/none".to_string()));
}

#[test]
fn test_parse_config() {
    let conf = parse_config("test/foo.toml".to_string()).unwrap();
    let gi = conf.gitignore.unwrap();
    assert_eq!(gi, vec!["rust".to_string(), "vim".to_string()]);

    let mkd = conf.mkdir.unwrap();
    assert_eq!(mkd, vec!["b".to_string()]);

    let touch = conf.touch.unwrap();
    assert_eq!(touch, vec!["a".to_string()]);

    let exec = conf.exec.unwrap();
    assert_eq!(exec, vec!["touch asdf".to_string()]);
}

#[test]
fn test_exec() {
    let (stdout, stderr) = exec(&"echo -n test".to_string()).unwrap();
    assert_eq!(stdout, "test".to_string());
    assert_eq!(stderr, "".to_string());
    let (stdout, stderr) = exec(&"echo -n test 1>&2".to_string()).unwrap();
    assert_eq!(stdout, "".to_string());
    assert_eq!(stderr, "test".to_string());
}
<commit_msg>Implement subcommand `new` and load configuration from `~/.skeleton`<commit_after>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::path::Path;
use std::process::{exit, Command};
use std::env;

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("Skeleton")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Skeleton project manager")
        .args_from_usage("-l, --lang=<LANG>  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let mut config_path = match env::home_dir() {
        Some(path) => {
            match path.to_str() {
                Some(p) => p.to_string(),
                None => {
                    println!("Could not resolve $HOME");
                    exit(1);
                }
            }
        }
        None => {
            println!("Could not resolve $HOME");
            exit(1);
        }
    };

    config_path.push_str("/.skeleton/");
    config_path.push_str(lang);
    config_path.push_str(".toml");

    let config: Config = match parse_config(config_path) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };

    if let Some(subcmd) = matches.subcommand_matches("new") {
        let name = subcmd.value_of("NAME").unwrap();
        if file_exists(&name.to_string()) {
            println!("A file or directory with that name already exists");
            exit(1);
        } else {
            match create_dir(name.clone()) {
                Ok(_) => println!("Created directory '{}'", name),
                Err(e) => {
                    println!("Could not create direcotry '{}'. {:?}", name, e);
                    exit(1);
                }
            }
            if let Err(e) = env::set_current_dir(Path::new(&*name)) {
                println!("Could not change directory!");
                exit(1);
            }
        }
    }

    if let Some(dirs) = config.mkdir {
        for dir in dirs {
            if !file_exists(&dir) {
                match create_dir(dir.clone()) {
                    Ok(_) => println!("Created directory '{}'", dir),
                    Err(e) => println!("Could not create directory '{}'. {:?}", dir, e),
                }
            } else {
                println!("Directory '{}' already exists", dir);
            }
        }
    }

    if let Some(gi) = config.gitignore {
        let ign = gitignore::get_gitignore(gi).unwrap();
        let file = OpenOptions::new()
            .create_new(true)
            .append(true)
            .open("gitignore");
        if let Ok(mut file) = file {
            writeln!(file, "{}", ign);
        } else {
            println!("Could not write .gitignore");
        }
    }

    if let Some(fnames) = config.touch {
        for file in fnames {
            if !create_file(&file) {
                println!("Could not create file '{}'", file);
            }
        }
    }

    if let Some(cmds) = config.exec {
        for cmd in cmds {
            println!("Executing '{}'", cmd);
            match exec(&cmd) {
                Ok((out, err)) => {
                    println!("stdout: {}", out);
                    println!("stderr: {}", err);
                }
                Err(e) => println!("Failed to execute: {:?}", e),
            }
        }
    }
}

fn file_exists(name: &String) -> bool {
    Path::new(&name).exists()
}

fn exec(cmd: &String) -> Result<(String, String)> {
    #[cfg(unix)]
    let shell = "sh";
    #[cfg(unix)]
    let arg1 = "-c";
    #[cfg(windows)]
    let shell = "cmd.exe";
    #[cfg(windows)]
    let arg1 = "/C";

    let out = match Command::new(shell).arg(arg1).arg(cmd).output() {
        Ok(o) => o,
        Err(e) => return Err(e),
    };

    let stdout = match String::from_utf8(out.stdout) {
        Ok(so) => so,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    let stderr = match String::from_utf8(out.stderr) {
        Ok(se) => se,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    Ok((stdout, stderr))
}

fn create_file(file_name: &String) -> bool {
    !file_exists(&file_name) &&
    match OpenOptions::new()
              .append(true)
              .create(true)
              .open(file_name) {
        Ok(_) => true,
        Err(_) => false,
    }
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}

#[test]
fn test_file_exists() {
    assert!(file_exists(&"test/foo.toml".to_string()));
    assert!(!file_exists(&"test/none".to_string()));
}

#[test]
fn test_parse_config() {
    let conf = parse_config("test/foo.toml".to_string()).unwrap();
    let gi = conf.gitignore.unwrap();
    assert_eq!(gi, vec!["rust".to_string(), "vim".to_string()]);

    let mkd = conf.mkdir.unwrap();
    assert_eq!(mkd, vec!["b".to_string()]);

    let touch = conf.touch.unwrap();
    assert_eq!(touch, vec!["a".to_string()]);

    let exec = conf.exec.unwrap();
    assert_eq!(exec, vec!["touch asdf".to_string()]);
}

#[test]
fn test_exec() {
    let (stdout, stderr) = exec(&"echo -n test".to_string()).unwrap();
    assert_eq!(stdout, "test".to_string());
    assert_eq!(stderr, "".to_string());
    let (stdout, stderr) = exec(&"echo -n test 1>&2".to_string()).unwrap();
    assert_eq!(stdout, "".to_string());
    assert_eq!(stderr, "test".to_string());
}
<|endoftext|>


<commit_before>extern crate image;
extern crate image_base64;
extern crate cgi;

use std::fs::File;
use std::path::Path;
use std::env;
use std::io;//::{self, Write};
use image::GenericImage;

//should be a trait.  I am not sure how to impliment one for only Vec <Vec <bool>> and not Vec <T>
fn boundless_insert(y: i64, x: i64, value: bool, img: &mut Vec<Vec <bool>>) {
	let mut tx = x;
	let mut ty = y;
	for i in 0..img.len(){
		let mut ttx = x;
		while ttx < 0 {
			img[i].insert(0, false);
			ttx += 1;
		}
		assert!(((x < 0) && (ttx == 0)) || (x == ttx), "Expected ttx = zero or ttx = x.  Found {}", ttx);
		while img[i].len() <= ttx as usize {
			img[i].push(false);
		}
		tx = ttx
	}
	let mut row:Vec <bool> = Vec::new();
	for i in 0..img[0].len() {
		row.push(false);
	}
	while ty < 0 {
		img.insert(0, row.clone());
		ty += 1;
	}
	assert!(((y < 0) && (ty == 0)) || (y == ty), "Expected ty = zero or ty = y.  Found {}", ty);
	while img.len() <= ty as usize {
		img.push(row.clone())
	}
	img[ty as usize][tx as usize] = value;
}

struct ImgBlob {
	blob_type: u8, //0: object, 1: line
	top_right: [usize; 2],
	middle: [usize; 2],
	text: String,
	bitmap: Vec <Vec <bool>>
}

impl ImgBlob {
	fn from_top_right(x: usize, y: usize, claim: &mut Vec <Vec <bool>>, img: &Vec <Vec <bool>>) -> Option<ImgBlob>{
		let mut right = x;
		let mut top = y; //pretty sure that this is not supposed to change and can be eliminated
		let mut bitmap: Vec <Vec <bool>> = Vec::new();
		let mut queue: Vec <[usize; 2]> = Vec::new();
		claim[y][x] = true;
		queue.push([y+1, x]);
		queue.push([y, x+1]);
		bitmap.push(vec![true]);
		while queue.len() > 0 {
			let tempx = queue[0][1];
			let tempy = queue[0][0];
			if (tempy < img.len()) && (tempx  0) && (tempx > 0) {
				if img[tempy][tempx] && !(claim[tempy][tempx]){
					boundless_insert(((tempy as i64)-(top as i64)), ((tempx as i64)-(right as i64)), true, &mut bitmap);
					if tempx < right {
						right = tempx;
					}
					if tempy < top {
						top = tempy;
					}
					claim[tempy][tempx] = true;
					if queue[0][1] > top {
						queue.push([tempy-1, tempx]);
					}
					queue.push([tempy, tempx-1]);
					queue.push([tempy+1, tempx]);
					queue.push([tempy, tempx+1]);
				}
			}
			queue.remove(0);
		}
		if (bitmap[0].len() + bitmap.len() > 6) && (bitmap.len() > 3) && (bitmap[0].len() > 3) {
			println!("Found: {} x {}", bitmap[0].len(), bitmap.len());
			Some(ImgBlob {
				blob_type: if (bitmap[0].len() / bitmap.len()) > 10 {1} else {0},
				top_right: [right, top],
				middle: [right+(bitmap[0].len()/2), top+(bitmap.len()/2)],
				text: String::from(""),
				bitmap: bitmap
			})
		} else {
			None
		}
	}
}

struct Section {
	Heading: ImgBlob,
	blobs: Vec <ImgBlob>
}

struct Chapter {
	heading: ImgBlob,
	blobs: Vec <ImgBlob>,
	Sections: Vec

}

fn main() {
	let mut claimed: Vec <Vec <bool>> = Vec::new();
	let mut thresh: Vec <Vec <bool>> = Vec::new();
	let mut rblobs: Vec <ImgBlob> = Vec::new();
	let mut gblobs: Vec <ImgBlob> = Vec::new();
	let mut bblobs: Vec <ImgBlob> = Vec::new();
	let img = image::open(&Path::new("target.jpg")).unwrap();
	let mut row:Vec <bool> = Vec::new();
	for x in 0..img.width() {
		row.push(false);
	}
	for y in 0..img.height() {
		claimed.push(row.clone());
		thresh.push(row.clone());
	}
	let rgbimg = img.to_rgb();
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[0] > 140) && (pixel[1] <= 170) && (pixel[2] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => rblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished R");
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[1] > 140) && (pixel[0] <= 170) && (pixel[2] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => gblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished G");
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[2] > 140) && (pixel[1] <= 170) && (pixel[0] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			imgbuf.put_pixel(x as u32, y as u32, image::Luma([if thresh[y][x] {255} else {0}]));
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => bblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished B");
}
<commit_msg>Removed more debug stuff<commit_after>extern crate image;
extern crate image_base64;
extern crate cgi;

use std::fs::File;
use std::path::Path;
use std::env;
use std::io;//::{self, Write};
use image::GenericImage;

//should be a trait.  I am not sure how to impliment one for only Vec <Vec <bool>> and not Vec <T>
fn boundless_insert(y: i64, x: i64, value: bool, img: &mut Vec<Vec <bool>>) {
	let mut tx = x;
	let mut ty = y;
	for i in 0..img.len(){
		let mut ttx = x;
		while ttx < 0 {
			img[i].insert(0, false);
			ttx += 1;
		}
		assert!(((x < 0) && (ttx == 0)) || (x == ttx), "Expected ttx = zero or ttx = x.  Found {}", ttx);
		while img[i].len() <= ttx as usize {
			img[i].push(false);
		}
		tx = ttx
	}
	let mut row:Vec <bool> = Vec::new();
	for i in 0..img[0].len() {
		row.push(false);
	}
	while ty < 0 {
		img.insert(0, row.clone());
		ty += 1;
	}
	assert!(((y < 0) && (ty == 0)) || (y == ty), "Expected ty = zero or ty = y.  Found {}", ty);
	while img.len() <= ty as usize {
		img.push(row.clone())
	}
	img[ty as usize][tx as usize] = value;
}

struct ImgBlob {
	blob_type: u8, //0: object, 1: line
	top_right: [usize; 2],
	middle: [usize; 2],
	text: String,
	bitmap: Vec <Vec <bool>>
}

impl ImgBlob {
	fn from_top_right(x: usize, y: usize, claim: &mut Vec <Vec <bool>>, img: &Vec <Vec <bool>>) -> Option<ImgBlob>{
		let mut right = x;
		let mut top = y; //pretty sure that this is not supposed to change and can be eliminated
		let mut bitmap: Vec <Vec <bool>> = Vec::new();
		let mut queue: Vec <[usize; 2]> = Vec::new();
		claim[y][x] = true;
		queue.push([y+1, x]);
		queue.push([y, x+1]);
		bitmap.push(vec![true]);
		while queue.len() > 0 {
			let tempx = queue[0][1];
			let tempy = queue[0][0];
			if (tempy < img.len()) && (tempx  0) && (tempx > 0) {
				if img[tempy][tempx] && !(claim[tempy][tempx]){
					boundless_insert(((tempy as i64)-(top as i64)), ((tempx as i64)-(right as i64)), true, &mut bitmap);
					if tempx < right {
						right = tempx;
					}
					if tempy < top {
						top = tempy;
					}
					claim[tempy][tempx] = true;
					if queue[0][1] > top {
						queue.push([tempy-1, tempx]);
					}
					queue.push([tempy, tempx-1]);
					queue.push([tempy+1, tempx]);
					queue.push([tempy, tempx+1]);
				}
			}
			queue.remove(0);
		}
		if (bitmap[0].len() + bitmap.len() > 6) && (bitmap.len() > 3) && (bitmap[0].len() > 3) {
			println!("Found: {} x {}", bitmap[0].len(), bitmap.len());
			Some(ImgBlob {
				blob_type: if (bitmap[0].len() / bitmap.len()) > 10 {1} else {0},
				top_right: [right, top],
				middle: [right+(bitmap[0].len()/2), top+(bitmap.len()/2)],
				text: String::from(""),
				bitmap: bitmap
			})
		} else {
			None
		}
	}
}

struct Section {
	Heading: ImgBlob,
	blobs: Vec <ImgBlob>
}

struct Chapter {
	heading: ImgBlob,
	blobs: Vec <ImgBlob>,
	Sections: Vec

}

fn main() {
	let mut claimed: Vec <Vec <bool>> = Vec::new();
	let mut thresh: Vec <Vec <bool>> = Vec::new();
	let mut rblobs: Vec <ImgBlob> = Vec::new();
	let mut gblobs: Vec <ImgBlob> = Vec::new();
	let mut bblobs: Vec <ImgBlob> = Vec::new();
	let img = image::open(&Path::new("target.jpg")).unwrap();
	let mut row:Vec <bool> = Vec::new();
	for x in 0..img.width() {
		row.push(false);
	}
	for y in 0..img.height() {
		claimed.push(row.clone());
		thresh.push(row.clone());
	}
	let rgbimg = img.to_rgb();
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[0] > 140) && (pixel[1] <= 170) && (pixel[2] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => rblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished R");
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[1] > 140) && (pixel[0] <= 170) && (pixel[2] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => gblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished G");
	for (x, y, pixel) in rgbimg.enumerate_pixels() {
		thresh[y as usize][x as usize] = if (pixel[2] > 140) && (pixel[1] <= 170) && (pixel[0] <= 170) {true} else {false};
	}
	for y in 0..thresh.len() {
		for x in 0..thresh[0].len() {
			if thresh[y][x] {
				match ImgBlob::from_top_right(x, y, &mut claimed, &thresh){
					Some(o) => bblobs.push(o),
					None => {},
				}
			}
		}
	}
	println!("Finished B");
}
<|endoftext|>


<commit_before>use clap::{Arg, App};
use rayon::iter::{
    IntoParallelRefIterator,
    ParallelIterator,
};

use std::fs;
use std::fs::DirEntry;
use std::path::Path;

use auto_image_cropper::imagecrop::ImageCrop;

fn crop_image(input_path: &str, output_path: &str) {
	let mut image = ImageCrop::open(&Path::new(input_path))
        .expect(&format!("Failed to read input file: '{}'", input_path));

    let (top_left_corner, bottom_right_corner) = image.calculate_corners();

	println!("Cropping rectangle (({0}, {1}), ({2}, {3})) from {4} to {5}",
        top_left_corner.x,
        top_left_corner.y,
        bottom_right_corner.x,
        bottom_right_corner.y,
        input_path,
        output_path
    );

	let sub_image = image.original.crop(
        top_left_corner.x,
        top_left_corner.y,
        bottom_right_corner.x - top_left_corner.x,
        bottom_right_corner.y - top_left_corner.y,
    );

	sub_image.save(output_path)
        .expect(&format!(
            "Failed to save input file: '{}' to output path: '{}'",
                input_path,
                output_path)
        );
}

fn main() {
	let arguments = App::new("auto-image-cropper")
        .version("0.1.5")
        .author("Ritiek Malhotra <ritiekmalhotra123@gmail.com>")
        .about("Removes extra white boundaries from images to correctly resize canvas.")

        .arg(Arg::with_name("input")
        .short("i")
        .long("input")
        .value_name("LOCATION")
        .required(true)
        .takes_value(true)
        .help("Location of input image/directory"))

        .arg(Arg::with_name("output")
        .short("o")
        .long("output")
        .value_name("LOCATION")
        .takes_value(true)
        .help("Location of output image/directory"))

        .get_matches();

	let input_path = arguments.value_of("input").unwrap();
	let output_path = arguments.value_of("output")
        .unwrap_or(input_path);
	let path_type = fs::metadata(input_path).unwrap();

	if path_type.is_file() {
		crop_image(input_path, output_path);
	} else {
		let input_files = fs::read_dir(input_path).unwrap();

		if !Path::new(output_path).exists() {
			let _ = fs::create_dir(output_path);
		}
		let mut files_to_crop: Vec<DirEntry> = Vec::with_capacity(10);
		for file in input_files {
			files_to_crop.push(file.unwrap());
		}
		println!("Vector length is {}", files_to_crop.len());

		files_to_crop
			.par_iter()
			.for_each(|file| {
				let img_in = file.path();
				let img_name = img_in.file_name();
				let img_out = Path::new(output_path).join(img_name.unwrap());

				crop_image(&(img_in.display().to_string()),
						&(img_out.into_os_string().into_string().unwrap()));
			});
	}
}
<commit_msg>Retab<commit_after>use clap::{Arg, App};
use rayon::iter::{
    IntoParallelRefIterator,
    ParallelIterator,
};

use std::fs;
use std::fs::DirEntry;
use std::path::Path;

use auto_image_cropper::imagecrop::ImageCrop;

fn crop_image(input_path: &str, output_path: &str) {
    let mut image = ImageCrop::open(&Path::new(input_path))
        .expect(&format!("Failed to read input file: '{}'", input_path));

    let (top_left_corner, bottom_right_corner) = image.calculate_corners();

    println!("Cropping rectangle (({0}, {1}), ({2}, {3})) from {4} to {5}",
        top_left_corner.x,
        top_left_corner.y,
        bottom_right_corner.x,
        bottom_right_corner.y,
        input_path,
        output_path
    );

    let sub_image = image.original.crop(
        top_left_corner.x,
        top_left_corner.y,
        bottom_right_corner.x - top_left_corner.x,
        bottom_right_corner.y - top_left_corner.y,
    );

    sub_image.save(output_path)
        .expect(&format!(
            "Failed to save input file: '{}' to output path: '{}'",
                input_path,
                output_path)
        );
}

fn main() {
    let arguments = App::new("auto-image-cropper")
        .version("0.1.5")
        .author("Ritiek Malhotra <ritiekmalhotra123@gmail.com>")
        .about("Removes extra white boundaries from images to correctly resize canvas.")

        .arg(Arg::with_name("input")
        .short("i")
        .long("input")
        .value_name("LOCATION")
        .required(true)
        .takes_value(true)
        .help("Location of input image/directory"))

        .arg(Arg::with_name("output")
        .short("o")
        .long("output")
        .value_name("LOCATION")
        .takes_value(true)
        .help("Location of output image/directory"))

        .get_matches();

    let input_path = arguments.value_of("input").unwrap();
    let output_path = arguments.value_of("output")
        .unwrap_or(input_path);
    let path_type = fs::metadata(input_path).unwrap();

    if path_type.is_file() {
        crop_image(input_path, output_path);
    } else {
        let input_files = fs::read_dir(input_path).unwrap();

        if !Path::new(output_path).exists() {
            let _ = fs::create_dir(output_path);
        }
        let mut files_to_crop: Vec<DirEntry> = Vec::with_capacity(10);
        for file in input_files {
            files_to_crop.push(file.unwrap());
        }
        println!("Vector length is {}", files_to_crop.len());

        files_to_crop
            .par_iter()
            .for_each(|file| {
                let img_in = file.path();
                let img_name = img_in.file_name();
                let img_out = Path::new(output_path).join(img_name.unwrap());

                crop_image(&(img_in.display().to_string()),
                        &(img_out.into_os_string().into_string().unwrap()));
            });
    }
}
<|endoftext|>


<commit_before>#[macro_use] extern crate clap;
#[macro_use] extern crate log;
extern crate config;
extern crate regex;

use cli::CliConfig;
use configuration::Configuration;
use runtime::{ImagLogger, Runtime};
use clap::App;

mod cli;
mod configuration;
mod runtime;
mod module;
mod storage;

fn main() {
    let early_logger = ImagLogger::early().unwrap();
    let yaml = load_yaml!("../etc/cli.yml");
    let app = App::from_yaml(yaml);
    let mut config = CliConfig::new(app);
    let configuration = Configuration::new(&config);

    let logger = ImagLogger::init(&configuration, &config);
    let rt = Runtime::new(configuration, config);

    info!("Hello, world!");
}
<commit_msg>Add debugging output in main.rs<commit_after>#[macro_use] extern crate clap;
#[macro_use] extern crate log;
extern crate config;
extern crate regex;

use cli::CliConfig;
use configuration::Configuration;
use runtime::{ImagLogger, Runtime};
use clap::App;

mod cli;
mod configuration;
mod runtime;
mod module;
mod storage;

fn main() {
    let yaml = load_yaml!("../etc/cli.yml");
    let app = App::from_yaml(yaml);
    let mut config = CliConfig::new(app);
    let configuration = Configuration::new(&config);

    let logger = ImagLogger::init(&configuration, &config);
    debug!("Logger created!");

    let rt = Runtime::new(configuration, config);
    debug!("Runtime created!");

    info!("Hello, world!");
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Gecko's definition of a pseudo-element.
//!
//! Note that a few autogenerated bits of this live in
//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably
//! need to update the checked-in files for Servo.

use crate::gecko_bindings::structs::{self, CSSPseudoElementType};
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::{ComputedValues, PropertyFlags};
use crate::selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::string_cache::Atom;
use crate::values::serialize_atom_identifier;
use cssparser::ToCss;
use std::fmt;
use thin_slice::ThinBoxedSlice;

include!(concat!(
    env!("OUT_DIR"),
    "/gecko/pseudo_element_definition.rs"
));

impl ::selectors::parser::PseudoElement for PseudoElement {
    type Impl = SelectorImpl;

    fn valid_after_slotted(&self) -> bool {
        // TODO(emilio): Remove this function or this comment after [1] is
        // resolved.
        //
        // [1]: https://github.com/w3c/csswg-drafts/issues/3150
        self.is_before_or_after()
    }

    fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
        if !self.supports_user_action_state() {
            return false;
        }

        return pseudo_class.is_safe_user_action_state();
    }
}

impl PseudoElement {
    /// Returns the kind of cascade type that a given pseudo is going to use.
    ///
    /// In Gecko we only compute ::before and ::after eagerly. We save the rules
    /// for anonymous boxes separately, so we resolve them as precomputed
    /// pseudos.
    ///
    /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
    pub fn cascade_type(&self) -> PseudoElementCascadeType {
        if self.is_eager() {
            debug_assert!(!self.is_anon_box());
            return PseudoElementCascadeType::Eager;
        }

        if self.is_precomputed() {
            return PseudoElementCascadeType::Precomputed;
        }

        PseudoElementCascadeType::Lazy
    }

    /// Whether the pseudo-element should inherit from the default computed
    /// values instead of from the parent element.
    ///
    /// This is not the common thing, but there are some pseudos (namely:
    /// ::backdrop), that shouldn't inherit from the parent element.
    pub fn inherits_from_default_values(&self) -> bool {
        matches!(*self, PseudoElement::Backdrop)
    }

    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
    #[inline]
    pub fn eager_index(&self) -> usize {
        EAGER_PSEUDOS
            .iter()
            .position(|p| p == self)
            .expect("Not an eager pseudo")
    }

    /// Creates a pseudo-element from an eager index.
    #[inline]
    pub fn from_eager_index(i: usize) -> Self {
        EAGER_PSEUDOS[i].clone()
    }

    /// Whether the current pseudo element is ::before or ::after.
    #[inline]
    pub fn is_before_or_after(&self) -> bool {
        self.is_before() || self.is_after()
    }

    /// Whether this pseudo-element is the ::before pseudo.
    #[inline]
    pub fn is_before(&self) -> bool {
        *self == PseudoElement::Before
    }

    /// Whether this pseudo-element is the ::after pseudo.
    #[inline]
    pub fn is_after(&self) -> bool {
        *self == PseudoElement::After
    }

    /// Whether this pseudo-element is ::first-letter.
    #[inline]
    pub fn is_first_letter(&self) -> bool {
        *self == PseudoElement::FirstLetter
    }

    /// Whether this pseudo-element is ::first-line.
    #[inline]
    pub fn is_first_line(&self) -> bool {
        *self == PseudoElement::FirstLine
    }

    /// Whether this pseudo-element is ::-moz-fieldset-content.
    #[inline]
    pub fn is_fieldset_content(&self) -> bool {
        *self == PseudoElement::FieldsetContent
    }

    /// Whether this pseudo-element is lazily-cascaded.
    #[inline]
    pub fn is_lazy(&self) -> bool {
        !self.is_eager() && !self.is_precomputed()
    }

    /// Whether this pseudo-element supports user action selectors.
    pub fn supports_user_action_state(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0
    }

    /// Whether this pseudo-element is enabled for all content.
    pub fn enabled_in_content(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) == 0
    }

    /// Whether this pseudo is enabled explicitly in UA sheets.
    pub fn enabled_in_ua_sheets(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS) != 0
    }

    /// Whether this pseudo is enabled explicitly in chrome sheets.
    pub fn enabled_in_chrome(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME) != 0
    }

    /// Whether this pseudo-element skips flex/grid container display-based
    /// fixup.
    #[inline]
    pub fn skip_item_display_fixup(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM) == 0
    }

    /// Whether this pseudo-element is precomputed.
    #[inline]
    pub fn is_precomputed(&self) -> bool {
        self.is_anon_box() && !self.is_tree_pseudo_element()
    }

    /// Property flag that properties must have to apply to this pseudo-element.
    #[inline]
    pub fn property_restriction(&self) -> Option<PropertyFlags> {
        match *self {
            PseudoElement::FirstLetter => Some(PropertyFlags::APPLIES_TO_FIRST_LETTER),
            PseudoElement::FirstLine => Some(PropertyFlags::APPLIES_TO_FIRST_LINE),
            PseudoElement::Placeholder => Some(PropertyFlags::APPLIES_TO_PLACEHOLDER),
            _ => None,
        }
    }

    /// Whether this pseudo-element should actually exist if it has
    /// the given styles.
    pub fn should_exist(&self, style: &ComputedValues) -> bool {
        if style.get_box().clone_display() == Display::None {
            return false;
        }

        if self.is_before_or_after() && style.ineffective_content_property() {
            return false;
        }

        true
    }
}
<commit_msg>style: Allow ::slotted()::placeholder.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Gecko's definition of a pseudo-element.
//!
//! Note that a few autogenerated bits of this live in
//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably
//! need to update the checked-in files for Servo.

use crate::gecko_bindings::structs::{self, CSSPseudoElementType};
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::{ComputedValues, PropertyFlags};
use crate::selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::string_cache::Atom;
use crate::values::serialize_atom_identifier;
use cssparser::ToCss;
use std::fmt;
use thin_slice::ThinBoxedSlice;

include!(concat!(
    env!("OUT_DIR"),
    "/gecko/pseudo_element_definition.rs"
));

impl ::selectors::parser::PseudoElement for PseudoElement {
    type Impl = SelectorImpl;

    // ::slotted() should support all tree-abiding pseudo-elements, see
    // https://drafts.csswg.org/css-scoping/#slotted-pseudo
    // https://drafts.csswg.org/css-pseudo-4/#treelike
    fn valid_after_slotted(&self) -> bool {
        matches!(
            *self,
            PseudoElement::Before |
            PseudoElement::After |
            PseudoElement::Placeholder
        )
    }

    fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
        if !self.supports_user_action_state() {
            return false;
        }

        return pseudo_class.is_safe_user_action_state();
    }
}

impl PseudoElement {
    /// Returns the kind of cascade type that a given pseudo is going to use.
    ///
    /// In Gecko we only compute ::before and ::after eagerly. We save the rules
    /// for anonymous boxes separately, so we resolve them as precomputed
    /// pseudos.
    ///
    /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
    pub fn cascade_type(&self) -> PseudoElementCascadeType {
        if self.is_eager() {
            debug_assert!(!self.is_anon_box());
            return PseudoElementCascadeType::Eager;
        }

        if self.is_precomputed() {
            return PseudoElementCascadeType::Precomputed;
        }

        PseudoElementCascadeType::Lazy
    }

    /// Whether the pseudo-element should inherit from the default computed
    /// values instead of from the parent element.
    ///
    /// This is not the common thing, but there are some pseudos (namely:
    /// ::backdrop), that shouldn't inherit from the parent element.
    pub fn inherits_from_default_values(&self) -> bool {
        matches!(*self, PseudoElement::Backdrop)
    }

    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
    #[inline]
    pub fn eager_index(&self) -> usize {
        EAGER_PSEUDOS
            .iter()
            .position(|p| p == self)
            .expect("Not an eager pseudo")
    }

    /// Creates a pseudo-element from an eager index.
    #[inline]
    pub fn from_eager_index(i: usize) -> Self {
        EAGER_PSEUDOS[i].clone()
    }

    /// Whether the current pseudo element is ::before or ::after.
    #[inline]
    pub fn is_before_or_after(&self) -> bool {
        self.is_before() || self.is_after()
    }

    /// Whether this pseudo-element is the ::before pseudo.
    #[inline]
    pub fn is_before(&self) -> bool {
        *self == PseudoElement::Before
    }

    /// Whether this pseudo-element is the ::after pseudo.
    #[inline]
    pub fn is_after(&self) -> bool {
        *self == PseudoElement::After
    }

    /// Whether this pseudo-element is ::first-letter.
    #[inline]
    pub fn is_first_letter(&self) -> bool {
        *self == PseudoElement::FirstLetter
    }

    /// Whether this pseudo-element is ::first-line.
    #[inline]
    pub fn is_first_line(&self) -> bool {
        *self == PseudoElement::FirstLine
    }

    /// Whether this pseudo-element is ::-moz-fieldset-content.
    #[inline]
    pub fn is_fieldset_content(&self) -> bool {
        *self == PseudoElement::FieldsetContent
    }

    /// Whether this pseudo-element is lazily-cascaded.
    #[inline]
    pub fn is_lazy(&self) -> bool {
        !self.is_eager() && !self.is_precomputed()
    }

    /// Whether this pseudo-element supports user action selectors.
    pub fn supports_user_action_state(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0
    }

    /// Whether this pseudo-element is enabled for all content.
    pub fn enabled_in_content(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) == 0
    }

    /// Whether this pseudo is enabled explicitly in UA sheets.
    pub fn enabled_in_ua_sheets(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS) != 0
    }

    /// Whether this pseudo is enabled explicitly in chrome sheets.
    pub fn enabled_in_chrome(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME) != 0
    }

    /// Whether this pseudo-element skips flex/grid container display-based
    /// fixup.
    #[inline]
    pub fn skip_item_display_fixup(&self) -> bool {
        (self.flags() & structs::CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM) == 0
    }

    /// Whether this pseudo-element is precomputed.
    #[inline]
    pub fn is_precomputed(&self) -> bool {
        self.is_anon_box() && !self.is_tree_pseudo_element()
    }

    /// Property flag that properties must have to apply to this pseudo-element.
    #[inline]
    pub fn property_restriction(&self) -> Option<PropertyFlags> {
        match *self {
            PseudoElement::FirstLetter => Some(PropertyFlags::APPLIES_TO_FIRST_LETTER),
            PseudoElement::FirstLine => Some(PropertyFlags::APPLIES_TO_FIRST_LINE),
            PseudoElement::Placeholder => Some(PropertyFlags::APPLIES_TO_PLACEHOLDER),
            _ => None,
        }
    }

    /// Whether this pseudo-element should actually exist if it has
    /// the given styles.
    pub fn should_exist(&self, style: &ComputedValues) -> bool {
        if style.get_box().clone_display() == Display::None {
            return false;
        }

        if self.is_before_or_after() && style.ineffective_content_property() {
            return false;
        }

        true
    }
}
<|endoftext|>


<commit_before>//
//   Copyright 2016 Andrew Hunter
//
//   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.
//

//!
//! # NDFA
//!
//! The acronym NDFA stands for 'Non-Deterministic Finite Automaton'. An NDFA is a state machine where each state can have
//! transitions for more than one target state - this allows it to be in more than one state at once, which is the property
//! that makes it non-deterministic.
//!
//! NDFAs can match patterns conforming to a regular language - that is, they match regular expressions. However, their
//! non-deterministic nature means that they are inconvenient to evaluate. Fortunately, for every NDFA there can be found
//! a DFA - a *Deterministic* Finite Automaton - which can be used to rapidly match strings of symbols against the expression.
//!
//! Every implementation of the `StateMachine` trait represents an NDFA. This particular implementation is useful because it
//! also implements `MutableStateMachine` so can be used as a means to build state machines that can be used to match patterns.
//!
//! An NDFA is created by calling the constructor:
//!
//! ```
//! # use ndfa::*;
//! let mut ndfa: Ndfa<u32, u32> = Ndfa::new();
//! ```
//!

use super::state_machine::*;
use std::collections::HashMap;
use std::collections::HashSet;

///
/// Represents a non-deterministic finite-state automata
///
#[derive(Clone)]
pub struct Ndfa<InputSymbol, OutputSymbol> where InputSymbol : Clone {
    /// Highest known state ID
    max_state: StateId,

    /// Transitions for each state
    transitions: Vec<Vec<(InputSymbol, StateId)>>,

    /// The states that are joined with the state specified by the index
    joined_with: Vec<Vec<StateId>>,

    /// Output symbols for each state
    output_symbols: HashMap<StateId, OutputSymbol>
}

impl<InputSymbol : Clone, OutputSymbol> Ndfa<InputSymbol, OutputSymbol> {
    ///
    /// Creates a new non-deterministic finite Automaton
    ///
    /// The NDFA will initially just consist of the start state 0. The methods in `MutableStateMachine` should be called to
    /// build it into a more useful structure.
    ///
    pub fn new() -> Ndfa<InputSymbol, OutputSymbol> {
        Ndfa { max_state: 0, transitions: vec![], joined_with: vec![], output_symbols: HashMap::new() }
    }

    ///
    /// Retrieves the complete set of states whose transitions should be returned due to joining for a given state
    ///
    #[inline]
    fn get_join_closure(&self, state: StateId) -> HashSet<StateId> {
        // The result is initially empty. We'll add the initial state in the first pass
        let mut result: HashSet<StateId> = HashSet::new();

        // Add in any joined states
        let mut stack = vec![];
        stack.push(state);

        while let Some(next_state) = stack.pop() {
            if !result.contains(&next_state) {
                // Add to the result
                result.insert(next_state);

                // Process any states that are joined to this one
                if (next_state as usize) < self.joined_with.len() {
                    let ref join_states = self.joined_with[next_state as usize];

                    for join_to in join_states {
                        stack.push(*join_to);
                    }
                }
            }
        }

        result
    }
}

impl<InputSymbol : Clone, OutputSymbol> StateMachine<InputSymbol, OutputSymbol> for Ndfa<InputSymbol, OutputSymbol> {
    fn count_states(&self) -> StateId {
        self.max_state + 1
    }

    fn get_transitions_for_state(&self, state: StateId) -> Vec<(InputSymbol, StateId)> {
        let empty           = vec![];

        // The transitions are all the transitions for this state, plus all the transitions in the states that are joined to it
        let joined_states   = self.get_join_closure(state);
        let merged          = joined_states.iter().flat_map(|join_state| {
            if (*join_state as usize) < self.transitions.len() {
                self.transitions[*join_state as usize].iter()
            } else {
                empty.iter()
            }
        });

        merged.map(|item| item.clone()).collect()
    }

    fn output_symbol_for_state(&self, state: StateId) -> Option<&OutputSymbol> {
        self.output_symbols.get(&state)
    }
}

impl<InputSymbol : Clone, OutputSymbol> MutableStateMachine<InputSymbol, OutputSymbol> for Ndfa<InputSymbol, OutputSymbol> {
    fn add_transition(&mut self, state: StateId, for_symbol: InputSymbol, new_state: StateId) {
        // Make sure that max_state reflects the highest state added by the user
        if new_state > self.max_state {
            self.max_state = new_state;
        }

        if state > self.max_state {
            self.max_state = state;
        }

        // Expand the transitions vector so that the new state has somewhere to go
        while self.transitions.len() <= state as usize {
            self.transitions.push(vec![]);
        }

        self.transitions[state as usize].push((for_symbol, new_state));
    }

    fn set_output_symbol(&mut self, state: StateId, new_output_symbol: OutputSymbol) {
        // Output symbols update the max state too
        if state > self.max_state {
            self.max_state = state;
        }

        self.output_symbols.insert(state, new_output_symbol);
    }

    fn join_states(&mut self, first_state: StateId, second_state: StateId) {
        // Joining states updates the overall state count_states
        if first_state > self.max_state {
            self.max_state = first_state;
        }

        if second_state > self.max_state {
            self.max_state = second_state;
        }

        // Expand the join table so we can add the first state
        while self.joined_with.len() <= first_state as usize {
            self.joined_with.push(vec![]);
        }

        // Join the second state to the first state
        self.joined_with[first_state as usize].push(second_state);
    }
}

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

    #[test]
    fn there_is_initially_one_state() {
        let ndfa: Ndfa<u32, u32> = Ndfa::new();

        assert!(ndfa.count_states() == 1);
    }

    #[test]
    fn adding_transition_updates_max_state_to_target() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn adding_output_symbol_updates_max_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.set_output_symbol(1, 128);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn adding_transition_updates_max_state_to_source() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn can_retrieve_transition() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        assert!(ndfa.get_transitions_for_state(1).len() == 1);
        assert!(ndfa.get_transitions_for_state(1)[0] == (42, 0));
    }

    #[test]
    fn can_add_multiple_transitions() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        ndfa.add_transition(1, 42, 2);

        assert!(ndfa.get_transitions_for_state(1).len() == 2);
        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 0)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 2)));
    }

    #[test]
    fn output_symbol_is_none_by_default() {
        let ndfa: Ndfa<u32, u32> = Ndfa::new();

        assert!(ndfa.output_symbol_for_state(0) == None);
        assert!(ndfa.output_symbol_for_state(1) == None);
    }

    #[test]
    fn can_set_output_symbol() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.set_output_symbol(0, 64);

        assert!(ndfa.output_symbol_for_state(0) == Some(&64));
        assert!(ndfa.output_symbol_for_state(1) == None);
    }

    #[test]
    fn join_states_attaches_transitions_to_first_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
    }

    #[test]
    fn adding_transition_after_join_updates_target() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.join_states(0, 1);
        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
    }

    #[test]
    fn join_updates_state_count() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.join_states(0, 1);

        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn join_states_does_not_attach_to_second_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);

        assert!(!ndfa.get_transitions_for_state(1).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(43, 2)));
    }

    #[test]
    fn join_states_recurses_to_further_states() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.add_transition(2, 44, 3);
        ndfa.join_states(0, 1);
        ndfa.join_states(1, 2);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(44, 3)));
    }

    #[test]
    fn join_loop_attaches_to_both_states() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);
        ndfa.join_states(1, 0);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));

        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(43, 2)));
    }
}
<commit_msg>Update the comments in NDFA<commit_after>//
//   Copyright 2016 Andrew Hunter
//
//   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.
//

//!
//! # NDFA
//!
//! The acronym NDFA stands for 'Non-Deterministic Finite Automaton'. An NDFA is a state machine where each state can have
//! transitions for more than one target state - this allows it to be in more than one state at once, which is the property
//! that makes it non-deterministic.
//!
//! NDFAs can match patterns conforming to a regular language - that is, they match regular expressions. However, their
//! non-deterministic nature means that they are inconvenient to evaluate. Fortunately, for every NDFA there can be found
//! a DFA - a *Deterministic* Finite Automaton - which can be used to rapidly match strings of symbols against the expression.
//!
//! Every implementation of the `StateMachine` trait represents an NDFA. This particular implementation is useful because it
//! also implements `MutableStateMachine` so can be used as a means to build state machines that can be used to match patterns.
//!
//! An NDFA is created by calling the constructor:
//!
//! ```
//! # use ndfa::*;
//! let mut ndfa: Ndfa<u32, u32> = Ndfa::new();
//! ```
//!

use super::state_machine::*;
use std::collections::HashMap;
use std::collections::HashSet;

///
/// Represents a non-deterministic finite-state automata
///
#[derive(Clone)]
pub struct Ndfa<InputSymbol, OutputSymbol> where InputSymbol : Clone {
    /// Highest known state ID
    max_state: StateId,

    /// Transitions for each state
    transitions: Vec<Vec<(InputSymbol, StateId)>>,

    /// The states that are joined with the state specified by the index
    joined_with: Vec<Vec<StateId>>,

    /// Output symbols for each state
    output_symbols: HashMap<StateId, OutputSymbol>
}

impl<InputSymbol : Clone, OutputSymbol> Ndfa<InputSymbol, OutputSymbol> {
    ///
    /// Creates a new non-deterministic finite Automaton
    ///
    /// The NDFA will initially just consist of the start state 0. The methods in `MutableStateMachine` should be called to
    /// build it into a more useful structure.
    ///
    pub fn new() -> Ndfa<InputSymbol, OutputSymbol> {
        Ndfa { max_state: 0, transitions: vec![], joined_with: vec![], output_symbols: HashMap::new() }
    }

    ///
    /// Retrieves the complete set of states whose transitions should be returned due to joining for a given state
    ///
    #[inline]
    fn get_join_closure(&self, state: StateId) -> HashSet<StateId> {
        // TODO: this is a great use-case for the BitSet collection (available in the nightlies)

        // The result is initially empty. We'll add the initial state in the first pass
        let mut result: HashSet<StateId> = HashSet::new();

        // Add in any joined states
        let mut stack = vec![];
        stack.push(state);

        while let Some(next_state) = stack.pop() {
            if !result.contains(&next_state) {
                // Add to the result
                result.insert(next_state);

                // Process any states that are joined to this one
                if (next_state as usize) < self.joined_with.len() {
                    let ref join_states = self.joined_with[next_state as usize];

                    for join_to in join_states {
                        stack.push(*join_to);
                    }
                }
            }
        }

        result
    }
}

impl<InputSymbol : Clone, OutputSymbol> StateMachine<InputSymbol, OutputSymbol> for Ndfa<InputSymbol, OutputSymbol> {
    ///
    /// Retrieves the number of states in this state machine
    ///
    fn count_states(&self) -> StateId {
        self.max_state + 1
    }

    ///
    /// Retrieves the transitions for a particular state
    ///
    fn get_transitions_for_state(&self, state: StateId) -> Vec<(InputSymbol, StateId)> {
        let empty           = vec![];

        // The transitions are all the transitions for this state, plus all the transitions in the states that are joined to it
        let joined_states   = self.get_join_closure(state);
        let merged          = joined_states.iter().flat_map(|join_state| {
            if (*join_state as usize) < self.transitions.len() {
                self.transitions[*join_state as usize].iter()
            } else {
                empty.iter()
            }
        });

        merged.map(|item| item.clone()).collect()
    }

    ///
    /// Retrieves the output symbol for a particular state
    ///
    fn output_symbol_for_state(&self, state: StateId) -> Option<&OutputSymbol> {
        self.output_symbols.get(&state)
    }
}

impl<InputSymbol : Clone, OutputSymbol> MutableStateMachine<InputSymbol, OutputSymbol> for Ndfa<InputSymbol, OutputSymbol> {
    ///
    /// Creates a new transition in the state machine
    ///
    fn add_transition(&mut self, state: StateId, for_symbol: InputSymbol, new_state: StateId) {
        // Make sure that max_state reflects the highest state added by the user
        if new_state > self.max_state {
            self.max_state = new_state;
        }

        if state > self.max_state {
            self.max_state = state;
        }

        // Expand the transitions vector so that the new state has somewhere to go
        while self.transitions.len() <= state as usize {
            self.transitions.push(vec![]);
        }

        self.transitions[state as usize].push((for_symbol, new_state));
    }

    ///
    /// Sets the output symbol for a particular state
    ///
    fn set_output_symbol(&mut self, state: StateId, new_output_symbol: OutputSymbol) {
        // Output symbols update the max state too
        if state > self.max_state {
            self.max_state = state;
        }

        self.output_symbols.insert(state, new_output_symbol);
    }

    ///
    /// Joins two states so that the first state has the same transitions as the second state
    ///
    fn join_states(&mut self, first_state: StateId, second_state: StateId) {
        // Joining states updates the overall state count_states
        if first_state > self.max_state {
            self.max_state = first_state;
        }

        if second_state > self.max_state {
            self.max_state = second_state;
        }

        // Expand the join table so we can add the first state
        while self.joined_with.len() <= first_state as usize {
            self.joined_with.push(vec![]);
        }

        // Join the second state to the first state
        self.joined_with[first_state as usize].push(second_state);
    }
}

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

    #[test]
    fn there_is_initially_one_state() {
        let ndfa: Ndfa<u32, u32> = Ndfa::new();

        assert!(ndfa.count_states() == 1);
    }

    #[test]
    fn adding_transition_updates_max_state_to_target() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn adding_output_symbol_updates_max_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.set_output_symbol(1, 128);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn adding_transition_updates_max_state_to_source() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn can_retrieve_transition() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        assert!(ndfa.get_transitions_for_state(1).len() == 1);
        assert!(ndfa.get_transitions_for_state(1)[0] == (42, 0));
    }

    #[test]
    fn can_add_multiple_transitions() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(1, 42, 0);
        ndfa.add_transition(1, 42, 2);

        assert!(ndfa.get_transitions_for_state(1).len() == 2);
        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 0)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 2)));
    }

    #[test]
    fn output_symbol_is_none_by_default() {
        let ndfa: Ndfa<u32, u32> = Ndfa::new();

        assert!(ndfa.output_symbol_for_state(0) == None);
        assert!(ndfa.output_symbol_for_state(1) == None);
    }

    #[test]
    fn can_set_output_symbol() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.set_output_symbol(0, 64);

        assert!(ndfa.output_symbol_for_state(0) == Some(&64));
        assert!(ndfa.output_symbol_for_state(1) == None);
    }

    #[test]
    fn join_states_attaches_transitions_to_first_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
    }

    #[test]
    fn adding_transition_after_join_updates_target() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.join_states(0, 1);
        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
    }

    #[test]
    fn join_updates_state_count() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.join_states(0, 1);

        assert!(ndfa.count_states() == 2);
    }

    #[test]
    fn join_states_does_not_attach_to_second_state() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);

        assert!(!ndfa.get_transitions_for_state(1).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(43, 2)));
    }

    #[test]
    fn join_states_recurses_to_further_states() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.add_transition(2, 44, 3);
        ndfa.join_states(0, 1);
        ndfa.join_states(1, 2);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(44, 3)));
    }

    #[test]
    fn join_loop_attaches_to_both_states() {
        let mut ndfa: Ndfa<u32, u32> = Ndfa::new();

        ndfa.add_transition(0, 42, 1);
        ndfa.add_transition(1, 43, 2);
        ndfa.join_states(0, 1);
        ndfa.join_states(1, 0);

        assert!(ndfa.get_transitions_for_state(0).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(0).contains(&(43, 2)));

        assert!(ndfa.get_transitions_for_state(1).contains(&(42, 1)));
        assert!(ndfa.get_transitions_for_state(1).contains(&(43, 2)));
    }
}
<|endoftext|>