<commit_before>use crate::action::finder::chunks::{
    get_file_chunks, Chunks, GetFileChunksConfig,
};
use crate::action::finder::request::{
    DownloadActionOptions, HashActionOptions,
};
use crate::fs::Entry;
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::{BufReader, Take};

#[derive(Debug)]
pub enum Response {
    /// Download action is not performed and no further action is required.
    Skip(),
    /// File was not downloaded, but hash action must be executed.
    HashRequest(HashActionOptions),
    /// Chunks of data to be uploaded.
    CollectData(Chunks<BufReader<Take<File>>>),
}

/// Performs `download` action logic and returns file contents to be uploaded
/// or another action to be executed.
pub fn download(entry: &Entry, config: &DownloadActionOptions) -> Response {
    if entry.metadata.len() > config.max_size {
        use rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::*;
        match config.oversized_file_policy {
            SKIP => {
                return Response::Skip();
            }
            DOWNLOAD_TRUNCATED => (),
            HASH_TRUNCATED => {
                let hash_config = HashActionOptions {
                    max_size: config.max_size,
                    oversized_file_policy:
                        rrg_proto::protobuf::flows::FileFinderHashActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
                };
                return Response::HashRequest(hash_config);
            }
        };
    }

    let chunks = get_file_chunks(
        &entry.path,
        &GetFileChunksConfig {
            max_read_bytes: config.max_size,
            bytes_per_chunk: config.chunk_size,
            start_offset: 0,
            overlap_bytes: 0,
        },
    );

    match chunks {
        Some(chunks) => Response::CollectData(chunks),
        None => Response::Skip(),
    }
}

/// A type representing unique identifier of a given chunk.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ChunkId {
    /// A SHA-256 digest of the referenced chunk data.
    sha256: [u8; 32],
    offset: u64,
    length: u64,
}

impl ChunkId {
    /// Creates a chunk identifier for the given chunk.
    pub fn make(chunk: &[u8], offset: u64) -> ChunkId {
        ChunkId {
            sha256: Sha256::digest(chunk).into(),
            length: chunk.len() as u64,
            offset,
        }
    }
}

#[derive(Debug)]
pub struct DownloadEntry {
    pub chunk_ids: Vec<ChunkId>,
    pub chunk_size: u64,
}

impl From<DownloadEntry> for rrg_proto::BlobImageDescriptor {
    fn from(entry: DownloadEntry) -> rrg_proto::BlobImageDescriptor {
        rrg_proto::BlobImageDescriptor {
            chunks: entry
                .chunk_ids
                .into_iter()
                .map(|x| rrg_proto::BlobImageChunkDescriptor {
                    offset: Some(x.offset),
                    length: Some(x.length),
                    digest: Some(x.sha256.to_vec()),
                })
                .collect::<Vec<_>>(),
            chunk_size: Some(entry.chunk_size),
        }
    }
}

/// A type representing a particular chunk of the returned timeline.
pub struct Chunk {
    pub data: Vec<u8>,
}

impl super::super::Response for Chunk {
    const RDF_NAME: Option<&'static str> = Some("DataBlob");

    type Proto = rrg_proto::protobuf::jobs::DataBlob;

    fn into_proto(self) -> rrg_proto::protobuf::jobs::DataBlob {
        self.data.into()
    }
}

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

    #[test]
    fn test_chunked_download() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_data").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 100,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "some_".bytes().collect::<Vec<_>>()
        );
        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "data".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_no_empty_chunk_download() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 100,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "some_".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_skip_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        assert!(matches!(result, Response::Skip()));
    }

    #[test]
    fn test_hash_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        assert!(matches!(
            result,
            Response::HashRequest(HashActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderHashActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
            })
        ));
    }

    #[test]
    fn test_download_truncated_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::DOWNLOAD_TRUNCATED,
                use_external_stores: false,
                chunk_size: 3,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "som".bytes().collect::<Vec<_>>()
        );
        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "e_".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_chunk_id() {
        let chunk = ChunkId::make(b"some_test_data", 5);
        assert_eq!(&chunk.length, &14);
        assert_eq!(&chunk.offset, &5);
        assert_eq!(
            &chunk.sha256,
            &[
                0xd7, 0x6d, 0x85, 0xad, 0xca, 0x8a, 0xfa, 0xd2, 0x05, 0xed,
                0xeb, 0xc1, 0x1f, 0x9b, 0x50, 0x86, 0xbc, 0xa7, 0x5a, 0xcb,
                0x51, 0x2a, 0x74, 0x8b, 0xc7, 0x96, 0x60, 0xe1, 0x34, 0x6a,
                0xf5, 0x46
            ]
        );
    }
}
<commit_msg>Implement conversions for finder download types.<commit_after>use crate::action::finder::chunks::{
    get_file_chunks, Chunks, GetFileChunksConfig,
};
use crate::action::finder::request::{
    DownloadActionOptions, HashActionOptions,
};
use crate::fs::Entry;
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::{BufReader, Take};

#[derive(Debug)]
pub enum Response {
    /// Download action is not performed and no further action is required.
    Skip(),
    /// File was not downloaded, but hash action must be executed.
    HashRequest(HashActionOptions),
    /// Chunks of data to be uploaded.
    CollectData(Chunks<BufReader<Take<File>>>),
}

/// Performs `download` action logic and returns file contents to be uploaded
/// or another action to be executed.
pub fn download(entry: &Entry, config: &DownloadActionOptions) -> Response {
    if entry.metadata.len() > config.max_size {
        use rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::*;
        match config.oversized_file_policy {
            SKIP => {
                return Response::Skip();
            }
            DOWNLOAD_TRUNCATED => (),
            HASH_TRUNCATED => {
                let hash_config = HashActionOptions {
                    max_size: config.max_size,
                    oversized_file_policy:
                        rrg_proto::protobuf::flows::FileFinderHashActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
                };
                return Response::HashRequest(hash_config);
            }
        };
    }

    let chunks = get_file_chunks(
        &entry.path,
        &GetFileChunksConfig {
            max_read_bytes: config.max_size,
            bytes_per_chunk: config.chunk_size,
            start_offset: 0,
            overlap_bytes: 0,
        },
    );

    match chunks {
        Some(chunks) => Response::CollectData(chunks),
        None => Response::Skip(),
    }
}

/// A type representing unique identifier of a given chunk.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ChunkId {
    /// A SHA-256 digest of the referenced chunk data.
    sha256: [u8; 32],
    offset: u64,
    length: u64,
}

impl ChunkId {
    /// Creates a chunk identifier for the given chunk.
    pub fn make(chunk: &[u8], offset: u64) -> ChunkId {
        ChunkId {
            sha256: Sha256::digest(chunk).into(),
            length: chunk.len() as u64,
            offset,
        }
    }
}

#[derive(Debug)]
pub struct DownloadEntry {
    pub chunk_ids: Vec<ChunkId>,
    pub chunk_size: u64,
}

impl From<DownloadEntry> for rrg_proto::BlobImageDescriptor {
    fn from(entry: DownloadEntry) -> rrg_proto::BlobImageDescriptor {
        rrg_proto::BlobImageDescriptor {
            chunks: entry
                .chunk_ids
                .into_iter()
                .map(|x| rrg_proto::BlobImageChunkDescriptor {
                    offset: Some(x.offset),
                    length: Some(x.length),
                    digest: Some(x.sha256.to_vec()),
                })
                .collect::<Vec<_>>(),
            chunk_size: Some(entry.chunk_size),
        }
    }
}

impl From<DownloadEntry> for rrg_proto::protobuf::jobs::BlobImageDescriptor {

    fn from(entry: DownloadEntry) -> rrg_proto::protobuf::jobs::BlobImageDescriptor {
        let chunks = entry.chunk_ids
            .into_iter()
            .map(|chunk_id| chunk_id.into())
            .collect();

        let mut proto = rrg_proto::protobuf::jobs::BlobImageDescriptor::new();
        proto.set_chunk_size(entry.chunk_size);
        proto.set_chunks(chunks);

        proto
    }
}

impl From<ChunkId> for rrg_proto::protobuf::jobs::BlobImageChunkDescriptor {

    fn from(chunk_id: ChunkId) -> rrg_proto::protobuf::jobs::BlobImageChunkDescriptor {
        let mut proto = rrg_proto::protobuf::jobs::BlobImageChunkDescriptor::new();
        proto.set_offset(chunk_id.offset);
        proto.set_length(chunk_id.length);
        proto.set_digest(chunk_id.sha256.to_vec());

        proto
    }
}

/// A type representing a particular chunk of the returned timeline.
pub struct Chunk {
    pub data: Vec<u8>,
}

impl super::super::Response for Chunk {
    const RDF_NAME: Option<&'static str> = Some("DataBlob");

    type Proto = rrg_proto::protobuf::jobs::DataBlob;

    fn into_proto(self) -> rrg_proto::protobuf::jobs::DataBlob {
        self.data.into()
    }
}

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

    #[test]
    fn test_chunked_download() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_data").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 100,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "some_".bytes().collect::<Vec<_>>()
        );
        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "data".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_no_empty_chunk_download() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 100,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "some_".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_skip_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::SKIP,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        assert!(matches!(result, Response::Skip()));
    }

    #[test]
    fn test_hash_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
                use_external_stores: false,
                chunk_size: 5,
            },
        );

        assert!(matches!(
            result,
            Response::HashRequest(HashActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderHashActionOptions_OversizedFilePolicy::HASH_TRUNCATED,
            })
        ));
    }

    #[test]
    fn test_download_truncated_above_max_size() {
        let tempdir = tempfile::tempdir().unwrap();
        let path = tempdir.path().join("f");
        std::fs::write(&path, "some_1").unwrap();
        let entry = Entry {
            metadata: path.metadata().unwrap(),
            path,
        };

        let result = download(
            &entry,
            &DownloadActionOptions {
                max_size: 5,
                oversized_file_policy: rrg_proto::protobuf::flows::FileFinderDownloadActionOptions_OversizedFilePolicy::DOWNLOAD_TRUNCATED,
                use_external_stores: false,
                chunk_size: 3,
            },
        );

        let mut chunks = match result {
            Response::CollectData(chunks) => chunks,
            _ => panic!("Unexpected result type."),
        };

        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "som".bytes().collect::<Vec<_>>()
        );
        assert_eq!(
            chunks.next().unwrap().unwrap(),
            "e_".bytes().collect::<Vec<_>>()
        );
        assert!(chunks.next().is_none());
    }

    #[test]
    fn test_chunk_id() {
        let chunk = ChunkId::make(b"some_test_data", 5);
        assert_eq!(&chunk.length, &14);
        assert_eq!(&chunk.offset, &5);
        assert_eq!(
            &chunk.sha256,
            &[
                0xd7, 0x6d, 0x85, 0xad, 0xca, 0x8a, 0xfa, 0xd2, 0x05, 0xed,
                0xeb, 0xc1, 0x1f, 0x9b, 0x50, 0x86, 0xbc, 0xa7, 0x5a, 0xcb,
                0x51, 0x2a, 0x74, 0x8b, 0xc7, 0x96, 0x60, 0xe1, 0x34, 0x6a,
                0xf5, 0x46
            ]
        );
    }
}
<|endoftext|>


<commit_before>mod stream;
pub use self::stream::TermStream;

use termion::{color, cursor, terminal_size, clear};
use irc::{Command, UserCommand, ClientTunnel, ClientSender, ClientReceiver};
use std::thread;
use std::time::Duration;
use std::io::Write;
use std::collections::VecDeque;

pub struct Terminal<S,R> where S: ClientSender<UserCommand>, R: ClientReceiver<Command> {
    tunnel: ClientTunnel<S, R, UserCommand, Command>,
    stream: TermStream,
    message_pane: MessagePane,
    text_input: TextInput,
}

impl<S,R> Terminal<S,R> where S: ClientSender<UserCommand>, R: ClientReceiver<Command> {
    pub fn new(tunnel: ClientTunnel<S, R, UserCommand, Command>) -> Terminal<S,R> {
        Terminal {
            tunnel: tunnel,
            stream: TermStream::new().unwrap(),
            message_pane: MessagePane::new(),
            text_input: TextInput::new(),
        }
    }

    pub fn run(&mut self) {
        loop {
            loop {
                match self.tunnel.try_read() {
                    Ok(Some(m)) => self.message_pane.add_message(m),
                    Ok(None) => break,
                    Err(_) => unimplemented!(),
                }
            }

            self.message_pane.render(&mut self.stream);
            match self.text_input.read(&mut self.stream) {
                Some(UserInput::Close) => break,
                Some(UserInput::Text(s)) => {
                    let msg = UserCommand::Nick(s);
                    self.tunnel.write(msg);
                },
                _ => (),
            }
            thread::sleep(Duration::from_millis(16));
        }
    }
}

enum UserInput {
    Close,
    Text(String),
}

struct TextInput {
    history: Vec<Vec<u8>>,
    line: Vec<u8>,
    read_buf: VecDeque<u8>,
}

impl TextInput {
    fn new() -> TextInput {
        TextInput {
            history: Vec::new(),
            line: Vec::new(),
            read_buf: VecDeque::new(),
        }
    }

    fn read(&mut self, stream: &mut TermStream) -> Option<UserInput> {
        let mut buf = [0;128];
        if let Ok(bytes) = stream.read(&mut buf) {
            self.read_buf.extend(&buf[0..bytes])
        }

        while self.read_buf.len() > 0 {
            match self.read_buf.pop_front() {
                Some(3) => return Some(UserInput::Close),
                Some(127) => {
                    self.line.pop();
                },
                Some(13) => {
                    let result = String::from_utf8(self.line.clone()).unwrap();
                    let mut new_item = Vec::new();
                    new_item.append(&mut self.line);
                    self.history.push(new_item);
                    return Some(UserInput::Text(result));
                },
                Some(10) => (),
                None => (),
                Some(c) => self.line.push(c),
            }
        }

        let (width, height) = terminal_size().unwrap();
        let spaces = [' ';1000];
        let line_end = (self.line.len() + 1) as u16;
        let space_end = (width - line_end) as usize;
        stream.write_all(&*format!("{}{}{}{}{}",
                                 cursor::Goto(1,height),
                                 color::Fg(color::LightWhite),
                                 ::std::str::from_utf8(&*self.line).unwrap(),
                                 String::from(&spaces[0..space_end]),
                                 cursor::Goto(line_end, height)
                                ).into_bytes());
        stream.flush();

        None
    }
}

struct MessagePane {
    messages: Vec<Command>,
}

impl MessagePane {
    fn new() -> MessagePane {
        MessagePane {
            messages: Vec::new(),
        }
    }

    fn add_message(&mut self, msg: Command) {
        self.messages.push(msg);
    }

    fn render(&self, stream: &mut TermStream) {

        let mut messages: Vec<Vec<u8>> = Vec::new();
        for msg in self.messages.iter().rev() {
            messages.push(msg.to_string().into_bytes());
        }

        let (width, height) = terminal_size().unwrap();
        let width = width as usize;
        let height = height as usize;

        let msg_space = width * (height - 1);
        let mut total_length = 0;
        let mut msgs_to_display = 0;
        for msg in &*messages {
            let mut msg_length = 0;
            for c in msg {
                msg_length += match c {
                    &10 => 0,
                    &13 => width - (msg_length % width),
                    _ => 1,
                };

            }
            total_length += msg_length;
            if total_length < msg_space {
                msgs_to_display += 1;
            } else {
                break;
            }
        }

        let spare_lines = height - 1 - msgs_to_display;
        let spaces = [b' ';1000];

        let mut out_buf = Vec::new();
        if spare_lines > 0 {
            for i in  0..spare_lines {
                out_buf.extend_from_slice(&spaces[0..width]);
                out_buf.push(b'\r');
                out_buf.push(b'\n');
            }
        }
        let mut messages: Vec<&Vec<u8>> = messages.as_slice().iter().collect();
        messages.reverse();
        let mut i = 0;
        for msg in messages {
            let text_end = msg.len()-2;
            let right_padding = width - (text_end % width);
            out_buf.extend_from_slice(&msg[0..text_end]);
            out_buf.extend_from_slice(&spaces[0..right_padding]);
            out_buf.append(&mut b"\r\n".to_vec());
            i += 1;
            if i > msgs_to_display {
                break;
            }
        }

        stream.write_all(&*format!("{}{}{}{}",
                                 cursor::Goto(1,1),
                                 color::Fg(color::White),
                                 String::from_utf8(out_buf).unwrap(),
                                 cursor::Goto(1,height as u16)
                                ).into_bytes());
        stream.flush();
    }
}
<commit_msg>Only redraw UI if content has updated<commit_after>mod stream;
pub use self::stream::TermStream;

use termion::{color, cursor, terminal_size, clear};
use irc::{Command, UserCommand, ClientTunnel, ClientSender, ClientReceiver};
use std::thread;
use std::time::Duration;
use std::io::Write;
use std::collections::VecDeque;

pub struct Terminal<S,R> where S: ClientSender<UserCommand>, R: ClientReceiver<Command> {
    tunnel: ClientTunnel<S, R, UserCommand, Command>,
    stream: TermStream,
    message_pane: MessagePane,
    text_input: TextInput,
}

impl<S,R> Terminal<S,R> where S: ClientSender<UserCommand>, R: ClientReceiver<Command> {
    pub fn new(tunnel: ClientTunnel<S, R, UserCommand, Command>) -> Terminal<S,R> {
        Terminal {
            tunnel: tunnel,
            stream: TermStream::new().unwrap(),
            message_pane: MessagePane::new(),
            text_input: TextInput::new(),
        }
    }

    pub fn run(&mut self) {
        let mut tick = 0;
        let mut size = self.get_size();
        loop {
            loop {
                match self.tunnel.try_read() {
                    Ok(Some(m)) => {
                        self.message_pane.add_message(m);
                        //Must redraw text box too as msesage pane overlaps
                        self.text_input.set_dirty();
                    },
                    Ok(None) => break,
                    Err(_) => unimplemented!(),
                }
            }

            // Force immediate redraw if term changes sizes
            let new_size = self.get_size();
            if new_size != size {
                self.message_pane.set_dirty();
                self.text_input.set_dirty();
                size = new_size;
            }

            self.message_pane.render(&mut self.stream);
            match self.text_input.read(&mut self.stream) {
                Some(UserInput::Close) => break,
                Some(UserInput::Text(s)) => {
                    let msg = UserCommand::Nick(s);
                    self.tunnel.write(msg);
                },
                _ => (),
            }

            thread::sleep(Duration::from_millis(16));
        }
    }

    pub fn get_size(&self) -> (u16, u16) {
        terminal_size().unwrap()
    }
}

enum UserInput {
    Close,
    Text(String),
}

struct TextInput {
    history: Vec<Vec<u8>>,
    line: Vec<u8>,
    read_buf: VecDeque<u8>,
    is_dirty: bool,
}

impl TextInput {
    fn new() -> TextInput {
        TextInput {
            history: Vec::new(),
            line: Vec::new(),
            read_buf: VecDeque::new(),
            is_dirty: true,
        }
    }

    fn set_dirty(&mut self) {
        self.is_dirty = true;
    }

    fn read(&mut self, stream: &mut TermStream) -> Option<UserInput> {
        let mut buf = [0;128];
        if let Ok(bytes) = stream.read(&mut buf) {
            self.read_buf.extend(&buf[0..bytes])
        }

        while self.read_buf.len() > 0 {
            match self.read_buf.pop_front() {
                Some(3) => return Some(UserInput::Close),
                Some(127) => {
                    self.set_dirty();
                    self.line.pop();
                },
                Some(13) => {
                    let result = String::from_utf8(self.line.clone()).unwrap();
                    let mut new_item = Vec::new();
                    new_item.append(&mut self.line);
                    self.history.push(new_item);
                    self.set_dirty();
                    return Some(UserInput::Text(result));
                },
                Some(10) => (),
                None => (),
                Some(c) => {
                    self.set_dirty();
                    self.line.push(c)
                },
            }
        }

        if !self.is_dirty { return None; }

        let (width, height) = terminal_size().unwrap();
        let spaces = [' ';1000];
        let line_end = (self.line.len() + 1) as u16;
        let space_end = (width - line_end) as usize;
        stream.write_all(&*format!("{}{}{}{}{}",
                                 cursor::Goto(1,height),
                                 color::Fg(color::LightWhite),
                                 ::std::str::from_utf8(&*self.line).unwrap(),
                                 String::from(&spaces[0..space_end]),
                                 cursor::Goto(line_end, height)
                                ).into_bytes());
        stream.flush();
        self.is_dirty = false;

        None
    }
}

struct MessagePane {
    messages: Vec<Command>,
    is_dirty: bool,
}

impl MessagePane {
    fn new() -> MessagePane {
        MessagePane {
            messages: Vec::new(),
            is_dirty: true,
        }
    }

    fn add_message(&mut self, msg: Command) {
        self.set_dirty();
        self.messages.push(msg);
    }

    fn set_dirty(&mut self) {
        self.is_dirty = true;
    }

    fn render(&mut self, stream: &mut TermStream) {
        if !self.is_dirty { return; }
        let mut messages: Vec<Vec<u8>> = Vec::new();
        for msg in self.messages.iter().rev() {
            messages.push(msg.to_string().into_bytes());
        }

        let (width, height) = terminal_size().unwrap();
        let width = width as usize;
        let height = height as usize;

        let msg_space = width * (height - 1);
        let mut total_length = 0;
        let mut msgs_to_display = 0;
        for msg in &*messages {
            let mut msg_length = 0;
            for c in msg {
                msg_length += match c {
                    &10 => 0,
                    &13 => width - (msg_length % width),
                    _ => 1,
                };

            }
            total_length += msg_length;
            if total_length < msg_space {
                msgs_to_display += 1;
            } else {
                break;
            }
        }

        let spare_lines = height - 1 - msgs_to_display;
        let spaces = [b' ';1000];

        let mut out_buf = Vec::new();
        if spare_lines > 0 {
            for i in  0..spare_lines {
                out_buf.extend_from_slice(&spaces[0..width]);
                out_buf.push(b'\r');
                out_buf.push(b'\n');
            }
        }
        let mut messages: Vec<&Vec<u8>> = messages.as_slice().iter().collect();
        messages.reverse();
        let mut i = 0;
        for msg in messages {
            let text_end = msg.len()-2;
            let right_padding = width - (text_end % width);
            out_buf.extend_from_slice(&msg[0..text_end]);
            out_buf.extend_from_slice(&spaces[0..right_padding]);
            out_buf.append(&mut b"\r\n".to_vec());
            i += 1;
            if i > msgs_to_display {
                break;
            }
        }

        stream.write_all(&*format!("{}{}{}{}",
                                 cursor::Goto(1,1),
                                 color::Fg(color::White),
                                 String::from_utf8(out_buf).unwrap(),
                                 cursor::Goto(1,height as u16)
                                ).into_bytes());
        stream.flush();
        self.is_dirty = false;
    }
}
<|endoftext|>


<commit_before>use std::convert::TryInto;

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
    Data, DeriveInput, Field, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Visibility,
};

/// Representing the struct we are deriving
pub struct Input {
    /// The input struct name
    pub name: Ident,
    /// The list of traits to derive passed to `soa_derive` attribute
    pub derives: Vec<Ident>,
    /// The list of fields in the struct
    pub fields: Vec<Field>,
    /// The struct overall visibility
    pub visibility: Visibility,

    pub vec_attrs: Vec<Meta>,
    pub slice_attrs: Vec<Meta>,
    pub ref_attrs: Vec<Meta>,
    pub ptr_attrs: Vec<Meta>,
}

impl Input {
    pub fn new(input: DeriveInput) -> Input {
        let fields = match input.data {
            Data::Struct(s) => s.fields.iter().cloned().collect::<Vec<_>>(),
            _ => panic!("#[derive(StructOfArray)] only supports structs."),
        };

        let mut derives: Vec<Ident> = vec![];
        let mut vec_attrs = Vec::new();
        let mut slice_attrs = Vec::new();
        let mut ref_attrs = Vec::new();
        let mut ptr_attrs = Vec::new();
        for attr in input.attrs {
            if let Ok(meta) = attr.parse_meta() {
                if meta.path().is_ident("soa_derive") {
                    match meta {
                        Meta::NameValue(MetaNameValue {
                            lit: Lit::Str(string),
                            ..
                        }) => {
                            for value in string.value().split(',') {
                                derives.push(Ident::new(value.trim(), Span::call_site()));
                            }
                        }
                        _ => panic!(
                            "expected #[soa_derive = \"Traits, To, Derive\"], got #[{}]",
                            quote!(#meta)
                        ),
                    }
                } else if meta.path().is_ident("soa_attr") {
                    match meta.clone() {
                        Meta::List(MetaList { nested, .. }) => {
                            let [soa_type, attr]: [NestedMeta; 2] =
                                nested.into_iter().collect::<Vec<_>>().try_into().unwrap_or_else(|_| panic!("expected #[soa_derive(\"Types, To, Add, Attribute\", \"Attributes\", got #[{}])]", quote!(#meta)));
                            let attr = match attr {
                                NestedMeta::Meta(meta) => meta,
                                NestedMeta::Lit(_) => {
                                    panic!("expected a attribute, got {}", quote!(attr))
                                }
                            };
                            match soa_type {
                                NestedMeta::Meta(Meta::Path(path)) => match path.get_ident() {
                                    Some(ident) => match ident.to_string().as_str() {
                                        "Vec" => vec_attrs.push(attr),
                                        "Slice" => slice_attrs.push(attr),
                                        "Ref" => ref_attrs.push(attr),
                                        "Ptr" => ptr_attrs.push(attr),
                                        _ => panic!("expected a soa type, got {}", ident),
                                    },
                                    None => {
                                        panic!("expected a soa type, got {}", quote!(#path))
                                    }
                                },
                                _ => {
                                    panic!("expected a soa type, got {}", quote!(#soa_type))
                                }
                            }
                        }
                        _ => panic!("expected #[soa_attr(...), got #[{}]]", quote!(#meta)),
                    }
                }
            }
        }

        Input {
            name: input.ident,
            derives: derives,
            fields: fields,
            visibility: input.vis,
            vec_attrs,
            slice_attrs,
            ref_attrs,
            ptr_attrs,
        }
    }

    pub fn derive(&self) -> TokenStream {
        if self.derives.is_empty() {
            TokenStream::new()
        } else {
            let derives = &self.derives;
            quote!(
                #[derive(
                    #(#derives,)*
                )]
            )
        }
    }

    pub fn derive_with_exceptions(&self) -> TokenStream {
        if self.derives.is_empty() {
            TokenStream::new()
        } else {
            let derives = &self.derives.iter()
                                       .cloned()
                                       .filter(|name| name != "Clone")
                                       .filter(|name| name != "Deserialize")
                                       .filter(|name| name != "Serialize")
                                       .collect::<Vec<_>>();
            quote!(
                #[derive(
                    #(#derives,)*
                )]
            )
        }
    }

    pub fn vec_name(&self) -> Ident {
        Ident::new(&format!("{}Vec", self.name), Span::call_site())
    }

    pub fn slice_name(&self) -> Ident {
        Ident::new(&format!("{}Slice", self.name), Span::call_site())
    }

    pub fn slice_mut_name(&self) -> Ident {
        Ident::new(&format!("{}SliceMut", self.name), Span::call_site())
    }

    pub fn ref_name(&self) -> Ident {
        Ident::new(&format!("{}Ref", self.name), Span::call_site())
    }

    pub fn ref_mut_name(&self) -> Ident {
        Ident::new(&format!("{}RefMut", self.name), Span::call_site())
    }

    pub fn ptr_name(&self) -> Ident {
        Ident::new(&format!("{}Ptr", self.name), Span::call_site())
    }

    pub fn ptr_mut_name(&self) -> Ident {
        Ident::new(&format!("{}PtrMut", self.name), Span::call_site())
    }
}
<commit_msg>fix: typo in panic<commit_after>use std::convert::TryInto;

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
    Data, DeriveInput, Field, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Visibility,
};

/// Representing the struct we are deriving
pub struct Input {
    /// The input struct name
    pub name: Ident,
    /// The list of traits to derive passed to `soa_derive` attribute
    pub derives: Vec<Ident>,
    /// The list of fields in the struct
    pub fields: Vec<Field>,
    /// The struct overall visibility
    pub visibility: Visibility,

    pub vec_attrs: Vec<Meta>,
    pub slice_attrs: Vec<Meta>,
    pub ref_attrs: Vec<Meta>,
    pub ptr_attrs: Vec<Meta>,
}

impl Input {
    pub fn new(input: DeriveInput) -> Input {
        let fields = match input.data {
            Data::Struct(s) => s.fields.iter().cloned().collect::<Vec<_>>(),
            _ => panic!("#[derive(StructOfArray)] only supports structs."),
        };

        let mut derives: Vec<Ident> = vec![];
        let mut vec_attrs = Vec::new();
        let mut slice_attrs = Vec::new();
        let mut ref_attrs = Vec::new();
        let mut ptr_attrs = Vec::new();
        for attr in input.attrs {
            if let Ok(meta) = attr.parse_meta() {
                if meta.path().is_ident("soa_derive") {
                    match meta {
                        Meta::NameValue(MetaNameValue {
                            lit: Lit::Str(string),
                            ..
                        }) => {
                            for value in string.value().split(',') {
                                derives.push(Ident::new(value.trim(), Span::call_site()));
                            }
                        }
                        _ => panic!(
                            "expected #[soa_derive = \"Traits, To, Derive\"], got #[{}]",
                            quote!(#meta)
                        ),
                    }
                } else if meta.path().is_ident("soa_attr") {
                    match meta.clone() {
                        Meta::List(MetaList { nested, .. }) => {
                            let [soa_type, attr]: [NestedMeta; 2] =
                                nested.into_iter().collect::<Vec<_>>().try_into().unwrap_or_else(|_| panic!("expected #[soa_attr(\"Types, To, Add, Attribute\", \"Attribute\")], got #[{}]", quote!(#meta)));
                            let attr = match attr {
                                NestedMeta::Meta(meta) => meta,
                                NestedMeta::Lit(_) => {
                                    panic!("expected a attribute, got {}", quote!(attr))
                                }
                            };
                            match soa_type {
                                NestedMeta::Meta(Meta::Path(path)) => match path.get_ident() {
                                    Some(ident) => match ident.to_string().as_str() {
                                        "Vec" => vec_attrs.push(attr),
                                        "Slice" => slice_attrs.push(attr),
                                        "Ref" => ref_attrs.push(attr),
                                        "Ptr" => ptr_attrs.push(attr),
                                        _ => panic!("expected a soa type, got {}", ident),
                                    },
                                    None => {
                                        panic!("expected a soa type, got {}", quote!(#path))
                                    }
                                },
                                _ => {
                                    panic!("expected a soa type, got {}", quote!(#soa_type))
                                }
                            }
                        }
                        _ => panic!("expected #[soa_attr(...)], got #[{}]", quote!(#meta)),
                    }
                }
            }
        }

        Input {
            name: input.ident,
            derives: derives,
            fields: fields,
            visibility: input.vis,
            vec_attrs,
            slice_attrs,
            ref_attrs,
            ptr_attrs,
        }
    }

    pub fn derive(&self) -> TokenStream {
        if self.derives.is_empty() {
            TokenStream::new()
        } else {
            let derives = &self.derives;
            quote!(
                #[derive(
                    #(#derives,)*
                )]
            )
        }
    }

    pub fn derive_with_exceptions(&self) -> TokenStream {
        if self.derives.is_empty() {
            TokenStream::new()
        } else {
            let derives = &self.derives.iter()
                                       .cloned()
                                       .filter(|name| name != "Clone")
                                       .filter(|name| name != "Deserialize")
                                       .filter(|name| name != "Serialize")
                                       .collect::<Vec<_>>();
            quote!(
                #[derive(
                    #(#derives,)*
                )]
            )
        }
    }

    pub fn vec_name(&self) -> Ident {
        Ident::new(&format!("{}Vec", self.name), Span::call_site())
    }

    pub fn slice_name(&self) -> Ident {
        Ident::new(&format!("{}Slice", self.name), Span::call_site())
    }

    pub fn slice_mut_name(&self) -> Ident {
        Ident::new(&format!("{}SliceMut", self.name), Span::call_site())
    }

    pub fn ref_name(&self) -> Ident {
        Ident::new(&format!("{}Ref", self.name), Span::call_site())
    }

    pub fn ref_mut_name(&self) -> Ident {
        Ident::new(&format!("{}RefMut", self.name), Span::call_site())
    }

    pub fn ptr_name(&self) -> Ident {
        Ident::new(&format!("{}Ptr", self.name), Span::call_site())
    }

    pub fn ptr_mut_name(&self) -> Ident {
        Ident::new(&format!("{}PtrMut", self.name), Span::call_site())
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

mod board;
mod board_parts;
mod sys;

use imgui::{ImGuiCond, Ui};
use imgui_sys;

use model::{Color, FieldCoord, Model};
use vec2::Vec2;
use self::board::board;
pub use self::sys::run;

#[derive(PartialEq)]
pub enum Event {
    Click(FieldCoord),
    Exchange,
    NewGame,
    Resign,
}

pub fn draw(ui: &Ui, size: (f32, f32), model: &Model) -> Option<Event> {
    unsafe {
        imgui_sys::igPushStyleVar(imgui_sys::ImGuiStyleVar::WindowRounding, 0.0);
    }

    let mut event = None;

    ui.main_menu_bar(|| {
        ui.menu(im_str!("Game")).build(|| {
            if ui.menu_item(im_str!("New game")).build() {
                insert_if_empty(&mut event, Event::NewGame);
            }
        });
    });

    ui.window(im_str!("Coerceo"))
        .size(size, ImGuiCond::Always)
        .position((0.0, 19.0), ImGuiCond::Once)
        .title_bar(false)
        .resizable(false)
        .movable(false)
        .build(|| {
            ui.text("Welcome to Coerceo!");

            if let Some(click) = board(model, Vec2::new(size.0 - 16.0, size.1 - 135.0)) {
                insert_if_empty(&mut event, click);
            }

            use model::GameResult::*;
            match model.game_result {
                BlackWin => ui.text("Black wins!"),
                WhiteWin => ui.text("White wins!"),
                InProgress => {
                    match model.turn {
                        Color::White => ui.text("It's white's turn."),
                        Color::Black => ui.text("It's black's turn."),
                    }

                    ui.text(format!(
                        "White has {} piece(s) left and {} captured hex(es).",
                        model.white_pieces, model.white_hexes,
                    ));
                    ui.text(format!(
                        "Black has {} piece(s) left and {} captured hex(es).",
                        model.black_pieces, model.black_hexes,
                    ));

                    if ui.button(im_str!("Resign"), Vec2::new(100.0, 20.0)) {
                        insert_if_empty(&mut event, Event::Resign);
                    }
                    ui.same_line(0.0);
                    if model.can_exchange()
                        && ui.button(im_str!("Exchange"), Vec2::new(100.0, 20.0))
                    {
                        insert_if_empty(&mut event, Event::Exchange);
                    }
                }
            }
        });

    unsafe {
        imgui_sys::igPopStyleVar(1);
    }

    event
}

fn insert_if_empty<T>(a: &mut Option<T>, b: T) {
    if a.is_none() {
        *a = Some(b);
    }
}
<commit_msg>Indicate exchange status on exchange button label<commit_after>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

mod board;
mod board_parts;
mod sys;

use imgui::{ImGuiCond, Ui};
use imgui_sys;

use model::{Color, FieldCoord, Model};
use vec2::Vec2;
use self::board::board;
pub use self::sys::run;

#[derive(PartialEq)]
pub enum Event {
    Click(FieldCoord),
    Exchange,
    NewGame,
    Resign,
}

pub fn draw(ui: &Ui, size: (f32, f32), model: &Model) -> Option<Event> {
    unsafe {
        imgui_sys::igPushStyleVar(imgui_sys::ImGuiStyleVar::WindowRounding, 0.0);
    }

    let mut event = None;

    ui.main_menu_bar(|| {
        ui.menu(im_str!("Game")).build(|| {
            if ui.menu_item(im_str!("New game")).build() {
                insert_if_empty(&mut event, Event::NewGame);
            }
        });
    });

    ui.window(im_str!("Coerceo"))
        .size(size, ImGuiCond::Always)
        .position((0.0, 19.0), ImGuiCond::Once)
        .title_bar(false)
        .resizable(false)
        .movable(false)
        .build(|| {
            ui.text("Welcome to Coerceo!");

            if let Some(click) = board(model, Vec2::new(size.0 - 16.0, size.1 - 135.0)) {
                insert_if_empty(&mut event, click);
            }

            use model::GameResult::*;
            match model.game_result {
                BlackWin => ui.text("Black wins!"),
                WhiteWin => ui.text("White wins!"),
                InProgress => {
                    match model.turn {
                        Color::White => ui.text("It's white's turn."),
                        Color::Black => ui.text("It's black's turn."),
                    }

                    ui.text(format!(
                        "White has {} piece(s) left and {} captured hex(es).",
                        model.white_pieces, model.white_hexes,
                    ));
                    ui.text(format!(
                        "Black has {} piece(s) left and {} captured hex(es).",
                        model.black_pieces, model.black_hexes,
                    ));

                    if ui.button(im_str!("Resign"), Vec2::new(100.0, 20.0)) {
                        insert_if_empty(&mut event, Event::Resign);
                    }
                    ui.same_line(0.0);
                    if model.can_exchange() {
                        let label = if model.exchanging {
                            im_str!("Stop Exchanging")
                        } else {
                            im_str!("Exchange")
                        };
                        if ui.button(label, Vec2::new(120.0, 20.0)) {
                            insert_if_empty(&mut event, Event::Exchange);
                        }
                    }
                }
            }
        });

    unsafe {
        imgui_sys::igPopStyleVar(1);
    }

    event
}

fn insert_if_empty<T>(a: &mut Option<T>, b: T) {
    if a.is_none() {
        *a = Some(b);
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017-2019 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

mod board;
mod board_parts;
mod sys;
mod vec2;

use imgui::{Condition, ImStr, MenuItem, Slider, StyleVar, Ui, Window};

use self::board::board;
pub use self::sys::run;
use self::vec2::Vec2;
use crate::model::{Color, ColorMap, GameType, Model, Player};
use crate::update::Event;

pub fn draw(ui: &Ui, size: [f32; 2], model: &Model) -> Option<Event> {
    let mut event = None;
    let mut window_states = model.window_states.borrow_mut();

    ui.main_menu_bar(|| {
        ui.menu(im_str!("Game"), true, || {
            MenuItem::new(im_str!("New game")).enabled(false).build(ui);

            ui.menu(im_str!("Laurentius"), true, || {
                player_options(ui, &mut event, GameType::Laurentius);
            });
            ui.menu(im_str!("Ocius"), true, || {
                player_options(ui, &mut event, GameType::Ocius);
            });

            ui.separator();

            MenuItem::new(im_str!("Rules")).enabled(false).build(ui);
            if ui.is_item_hovered() {
                ui.tooltip_text("Any changes to the rules apply at the start of the next game.");
            }

            MenuItem::new(im_str!("One tile to exchange"))
                .build_with_ref(ui, &mut model.exchange_one_hex.borrow_mut());
            if ui.is_item_hovered() {
                ui.tooltip_text(
                    "If selected, only one tile (rather than two) is needed to exchange for a piece."
                );
            }

            ui.separator();

            if MenuItem::new(im_str!("Quit")).build(ui) {
                insert_if_empty(&mut event, Event::Quit);
            }
        });

        ui.menu(im_str!("Computer"), true, || {
            Slider::new(im_str!("Search depth"), 1..=7)
                .build(ui, &mut model.ai_search_depth.borrow_mut());
            if ui.is_item_hovered() {
                ui.tooltip_text(
                    "How many moves ahead the computer will search.\nFewer moves is \
                     faster and easier, while more moves is slower and more difficult.",
                );
            }

            MenuItem::new(im_str!("Show debug info")).build_with_ref(ui, &mut window_states.ai_debug);
        });

        ui.menu(im_str!("Help"), true, || {
            MenuItem::new(im_str!("How to Play")).build_with_ref(ui, &mut window_states.how_to_play);
            MenuItem::new(im_str!("About")).build_with_ref(ui, &mut window_states.about);
        });
    });

    let token = ui.push_style_var(StyleVar::WindowRounding(0.0));
    draw_window(ui, size, model, &mut event);
    token.pop(ui);

    if window_states.ai_debug {
        Window::new(im_str!("AI Debug Info"))
            .opened(&mut window_states.ai_debug)
            .size([300.0, 600.0], Condition::FirstUseEver)
            .build(ui, || {
                if let Ok(debug_info) = model.ai.debug_info.read() {
                    ui.text(debug_info.clone());
                }
            });
    }

    if window_states.how_to_play {
        // TODO: Create an interactive, in-game tutorial to teach the rules of the game
        Window::new(im_str!("How to Play"))
            .opened(&mut window_states.how_to_play)
            .build(ui, || {
                ui.text(
                    "Unfortunately, there isn't an in-game tutorial. Sorry!\nSee coerceo.com for \
                     the rules of the game.",
                );
            });
    }

    if window_states.about {
        Window::new(im_str!("About"))
            .opened(&mut window_states.about)
            .build(ui, || {
                ui.text(
                    "Coerceo v1.0.0 (https://github.com/NPN/coerceo)

An unofficial clone of a strategic board game.

Copyright (C) 2017-2019 Ryan Huang
This program comes with absolutely no warranty.
See the GNU AGPL, version 3 or later, for details.

All rights, trademarks, copyrights, concepts, etc. of the game
Coerceo belong to the Coerceo Company.

This program includes work from the following sources:

imgui-rs (https://github.com/Gekkio/imgui-rs)
Copyright (c) 2015-2017 The imgui-rs Developers
Licensed under the MIT License.

Fira Sans (https://github.com/mozilla/Fira)
Copyright (c) 2012-2015 The Mozilla Foundation and Telefonica S.A.
Licensed under the SIL Open Font License v1.1",
                );
            });
    }

    event
}

fn player_options(ui: &Ui, event: &mut Option<Event>, game_type: GameType) {
    use self::Player::*;
    if MenuItem::new(im_str!("Human vs. Human")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Human, Human)),
        );
    }
    if MenuItem::new(im_str!("Human vs. Computer")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Human, Computer)),
        );
    }
    if MenuItem::new(im_str!("Computer vs. Human")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Computer, Human)),
        );
    }
    if MenuItem::new(im_str!("Computer vs. Computer")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Computer, Computer)),
        );
    }
}

fn draw_window(ui: &Ui, size: [f32; 2], model: &Model, event: &mut Option<Event>) {
    Window::new(im_str!("Coerceo"))
        .size(size, Condition::Always)
        .position([0.0, 27.0], Condition::Always)
        .title_bar(false)
        .resizable(false)
        .movable(false)
        .bring_to_front_on_focus(false)
        .build(ui, || {
            ui.text("Welcome to Coerceo!");

            let exchange_hex_string = if model.board.hexes_to_exchange == 1 {
                "One tile to exchange"
            } else {
                "Two tiles to exchange"
            };
            ui.text(format!(
                "{:?} vs. {:?} ({})",
                model.players.white, model.players.black, exchange_hex_string
            ));

            if let Some(click) = board(ui, model, Vec2::new(size[0] - 16.0, size[1] - 232.0)) {
                insert_if_empty(event, click);
            }

            let format_piece_count = |count| match count {
                1 => String::from("1 piece"),
                _ => format!("{} pieces", count),
            };

            let format_hex_count = |count| match count {
                1 => String::from("1 captured tile"),
                _ => format!("{} captured tiles", count),
            };

            let display_vitals = || {
                ui.text(format!(
                    "{:?} has {} and {}.",
                    Color::White,
                    format_piece_count(model.board.pieces(Color::White)),
                    format_hex_count(model.board.hexes(Color::White)),
                ));
                ui.text(format!(
                    "{:?} has {} and {}.",
                    Color::Black,
                    format_piece_count(model.board.pieces(Color::Black)),
                    format_hex_count(model.board.hexes(Color::Black)),
                ));
            };

            let button_size = [155.0, 29.0];
            use crate::model::Outcome::*;
            match model.outcome {
                Win(color) => {
                    ui.text(format!("{:?} wins!", color));
                    display_vitals();
                    if model.can_undo() && ui.button(im_str!("Undo"), button_size) {
                        insert_if_empty(event, Event::Undo);
                    }
                }
                InProgress => {
                    if model.players.white == model.players.black {
                        ui.text(format!("It's {:?}'s turn.", model.board.turn,));
                    } else {
                        ui.text(match model.current_player() {
                            Player::Computer => "Waiting for the computer...",
                            Player::Human => "It's your turn.",
                        });
                    }

                    display_vitals();

                    horz_button_layout(
                        ui,
                        vec![
                            (model.can_undo(), im_str!("Undo"), Event::Undo),
                            (model.can_redo(), im_str!("Redo"), Event::Redo),
                        ],
                        button_size,
                        event,
                    );
                    let is_human_player = model.current_player() == Player::Human;
                    horz_button_layout(
                        ui,
                        vec![
                            (is_human_player, im_str!("Resign"), Event::Resign),
                            (
                                model.board.can_exchange() && is_human_player,
                                if model.exchanging {
                                    im_str!("Stop Exchanging")
                                } else {
                                    im_str!("Exchange")
                                },
                                Event::Exchange,
                            ),
                        ],
                        button_size,
                        event,
                    );
                }
                // Draw cases
                _ => {
                    let message = match model.outcome {
                        DrawStalemate => "It's a draw by stalemate!",
                        DrawThreefoldRepetition => "It's a draw by threefold repetition!",
                        DrawInsufficientMaterial => "It's a draw by insufficient material!",
                        _ => unreachable!(),
                    };
                    ui.text(message);
                    display_vitals();
                    if model.can_undo() && ui.button(im_str!("Undo"), button_size) {
                        insert_if_empty(event, Event::Undo);
                    }
                }
            }
        });
}

fn horz_button_layout(
    ui: &Ui,
    buttons: Vec<(bool, &ImStr, Event)>,
    size: [f32; 2],
    event: &mut Option<Event>,
) {
    if !buttons.iter().any(|&(show, _, _)| show) {
        return;
    }

    for (show, label, action) in buttons {
        if show {
            if ui.button(label, size) {
                insert_if_empty(event, action);
            }
        } else {
            ui.dummy(size);
        }
        ui.same_line(0.0);
    }
    ui.new_line();
}

fn insert_if_empty<T>(a: &mut Option<T>, b: T) {
    if a.is_none() {
        *a = Some(b);
    }
}
<commit_msg>Enforce minimum board size<commit_after>/*
 * Copyright (C) 2017-2019 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

mod board;
mod board_parts;
mod sys;
mod vec2;

use imgui::{Condition, ImStr, MenuItem, Slider, StyleVar, Ui, Window};

use self::board::board;
pub use self::sys::run;
use self::vec2::Vec2;
use crate::model::{Color, ColorMap, GameType, Model, Player};
use crate::update::Event;

pub fn draw(ui: &Ui, size: [f32; 2], model: &Model) -> Option<Event> {
    let mut event = None;
    let mut window_states = model.window_states.borrow_mut();

    ui.main_menu_bar(|| {
        ui.menu(im_str!("Game"), true, || {
            MenuItem::new(im_str!("New game")).enabled(false).build(ui);

            ui.menu(im_str!("Laurentius"), true, || {
                player_options(ui, &mut event, GameType::Laurentius);
            });
            ui.menu(im_str!("Ocius"), true, || {
                player_options(ui, &mut event, GameType::Ocius);
            });

            ui.separator();

            MenuItem::new(im_str!("Rules")).enabled(false).build(ui);
            if ui.is_item_hovered() {
                ui.tooltip_text("Any changes to the rules apply at the start of the next game.");
            }

            MenuItem::new(im_str!("One tile to exchange"))
                .build_with_ref(ui, &mut model.exchange_one_hex.borrow_mut());
            if ui.is_item_hovered() {
                ui.tooltip_text(
                    "If selected, only one tile (rather than two) is needed to exchange for a piece."
                );
            }

            ui.separator();

            if MenuItem::new(im_str!("Quit")).build(ui) {
                insert_if_empty(&mut event, Event::Quit);
            }
        });

        ui.menu(im_str!("Computer"), true, || {
            Slider::new(im_str!("Search depth"), 1..=7)
                .build(ui, &mut model.ai_search_depth.borrow_mut());
            if ui.is_item_hovered() {
                ui.tooltip_text(
                    "How many moves ahead the computer will search.\nFewer moves is \
                     faster and easier, while more moves is slower and more difficult.",
                );
            }

            MenuItem::new(im_str!("Show debug info")).build_with_ref(ui, &mut window_states.ai_debug);
        });

        ui.menu(im_str!("Help"), true, || {
            MenuItem::new(im_str!("How to Play")).build_with_ref(ui, &mut window_states.how_to_play);
            MenuItem::new(im_str!("About")).build_with_ref(ui, &mut window_states.about);
        });
    });

    let token = ui.push_style_var(StyleVar::WindowRounding(0.0));
    draw_window(ui, size, model, &mut event);
    token.pop(ui);

    if window_states.ai_debug {
        Window::new(im_str!("AI Debug Info"))
            .opened(&mut window_states.ai_debug)
            .size([300.0, 600.0], Condition::FirstUseEver)
            .build(ui, || {
                if let Ok(debug_info) = model.ai.debug_info.read() {
                    ui.text(debug_info.clone());
                }
            });
    }

    if window_states.how_to_play {
        // TODO: Create an interactive, in-game tutorial to teach the rules of the game
        Window::new(im_str!("How to Play"))
            .opened(&mut window_states.how_to_play)
            .build(ui, || {
                ui.text(
                    "Unfortunately, there isn't an in-game tutorial. Sorry!\nSee coerceo.com for \
                     the rules of the game.",
                );
            });
    }

    if window_states.about {
        Window::new(im_str!("About"))
            .opened(&mut window_states.about)
            .build(ui, || {
                ui.text(
                    "Coerceo v1.0.0 (https://github.com/NPN/coerceo)

An unofficial clone of a strategic board game.

Copyright (C) 2017-2019 Ryan Huang
This program comes with absolutely no warranty.
See the GNU AGPL, version 3 or later, for details.

All rights, trademarks, copyrights, concepts, etc. of the game
Coerceo belong to the Coerceo Company.

This program includes work from the following sources:

imgui-rs (https://github.com/Gekkio/imgui-rs)
Copyright (c) 2015-2017 The imgui-rs Developers
Licensed under the MIT License.

Fira Sans (https://github.com/mozilla/Fira)
Copyright (c) 2012-2015 The Mozilla Foundation and Telefonica S.A.
Licensed under the SIL Open Font License v1.1",
                );
            });
    }

    event
}

fn player_options(ui: &Ui, event: &mut Option<Event>, game_type: GameType) {
    use self::Player::*;
    if MenuItem::new(im_str!("Human vs. Human")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Human, Human)),
        );
    }
    if MenuItem::new(im_str!("Human vs. Computer")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Human, Computer)),
        );
    }
    if MenuItem::new(im_str!("Computer vs. Human")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Computer, Human)),
        );
    }
    if MenuItem::new(im_str!("Computer vs. Computer")).build(ui) {
        insert_if_empty(
            event,
            Event::NewGame(game_type, ColorMap::new(Computer, Computer)),
        );
    }
}

fn draw_window(ui: &Ui, size: [f32; 2], model: &Model, event: &mut Option<Event>) {
    Window::new(im_str!("Coerceo"))
        .size(size, Condition::Always)
        .position([0.0, 27.0], Condition::Always)
        .title_bar(false)
        .resizable(false)
        .movable(false)
        .bring_to_front_on_focus(false)
        .build(ui, || {
            ui.text("Welcome to Coerceo!");

            let exchange_hex_string = if model.board.hexes_to_exchange == 1 {
                "One tile to exchange"
            } else {
                "Two tiles to exchange"
            };
            ui.text(format!(
                "{:?} vs. {:?} ({})",
                model.players.white, model.players.black, exchange_hex_string
            ));

            let board_size = Vec2::new((size[0] - 16.0).max(100.0), (size[1] - 232.0).max(100.0));
            if let Some(click) = board(ui, model, board_size) {
                insert_if_empty(event, click);
            }

            let format_piece_count = |count| match count {
                1 => String::from("1 piece"),
                _ => format!("{} pieces", count),
            };

            let format_hex_count = |count| match count {
                1 => String::from("1 captured tile"),
                _ => format!("{} captured tiles", count),
            };

            let display_vitals = || {
                ui.text(format!(
                    "{:?} has {} and {}.",
                    Color::White,
                    format_piece_count(model.board.pieces(Color::White)),
                    format_hex_count(model.board.hexes(Color::White)),
                ));
                ui.text(format!(
                    "{:?} has {} and {}.",
                    Color::Black,
                    format_piece_count(model.board.pieces(Color::Black)),
                    format_hex_count(model.board.hexes(Color::Black)),
                ));
            };

            let button_size = [155.0, 29.0];
            use crate::model::Outcome::*;
            match model.outcome {
                Win(color) => {
                    ui.text(format!("{:?} wins!", color));
                    display_vitals();
                    if model.can_undo() && ui.button(im_str!("Undo"), button_size) {
                        insert_if_empty(event, Event::Undo);
                    }
                }
                InProgress => {
                    if model.players.white == model.players.black {
                        ui.text(format!("It's {:?}'s turn.", model.board.turn,));
                    } else {
                        ui.text(match model.current_player() {
                            Player::Computer => "Waiting for the computer...",
                            Player::Human => "It's your turn.",
                        });
                    }

                    display_vitals();

                    horz_button_layout(
                        ui,
                        vec![
                            (model.can_undo(), im_str!("Undo"), Event::Undo),
                            (model.can_redo(), im_str!("Redo"), Event::Redo),
                        ],
                        button_size,
                        event,
                    );
                    let is_human_player = model.current_player() == Player::Human;
                    horz_button_layout(
                        ui,
                        vec![
                            (is_human_player, im_str!("Resign"), Event::Resign),
                            (
                                model.board.can_exchange() && is_human_player,
                                if model.exchanging {
                                    im_str!("Stop Exchanging")
                                } else {
                                    im_str!("Exchange")
                                },
                                Event::Exchange,
                            ),
                        ],
                        button_size,
                        event,
                    );
                }
                // Draw cases
                _ => {
                    let message = match model.outcome {
                        DrawStalemate => "It's a draw by stalemate!",
                        DrawThreefoldRepetition => "It's a draw by threefold repetition!",
                        DrawInsufficientMaterial => "It's a draw by insufficient material!",
                        _ => unreachable!(),
                    };
                    ui.text(message);
                    display_vitals();
                    if model.can_undo() && ui.button(im_str!("Undo"), button_size) {
                        insert_if_empty(event, Event::Undo);
                    }
                }
            }
        });
}

fn horz_button_layout(
    ui: &Ui,
    buttons: Vec<(bool, &ImStr, Event)>,
    size: [f32; 2],
    event: &mut Option<Event>,
) {
    if !buttons.iter().any(|&(show, _, _)| show) {
        return;
    }

    for (show, label, action) in buttons {
        if show {
            if ui.button(label, size) {
                insert_if_empty(event, action);
            }
        } else {
            ui.dummy(size);
        }
        ui.same_line(0.0);
    }
    ui.new_line();
}

fn insert_if_empty<T>(a: &mut Option<T>, b: T) {
    if a.is_none() {
        *a = Some(b);
    }
}
<|endoftext|>


<commit_before>//! Hand-written binding to the very minimal part of libxml2 that we need.

#![allow(non_snake_case, non_camel_case_types)]

use glib_sys::gpointer;

pub const XML_CHAR_ENCODING_NONE: libc::c_int = 0;

pub const XML_INTERNAL_GENERAL_ENTITY: libc::c_int = 1;

pub const XML_PARSE_NONET: libc::c_int = 1 << 11;
pub const XML_PARSE_HUGE: libc::c_int = 1 << 19;
pub const XML_PARSE_BIG_LINES: libc::c_int = 1 << 22;

pub const XML_SAX2_MAGIC: libc::c_uint = 0xDEEDBEAF;

pub type xmlDocPtr = gpointer;

pub type xmlEntityPtr = gpointer;

pub type UnusedFn = Option<unsafe extern "C" fn()>;

#[rustfmt::skip]
#[repr(C)]
pub struct xmlSAXHandler {
    pub internalSubset:    UnusedFn,
    pub isStandalone:      UnusedFn,
    pub hasInternalSubset: UnusedFn,
    pub hasExternalSubset: UnusedFn,
    pub resolveEntity:     UnusedFn,

    pub getEntity: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
    ) -> xmlEntityPtr>,

    pub entityDecl: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
        type_: libc::c_int,
        public_id: *const libc::c_char,
        system_id: *const libc::c_char,
        content: *const libc::c_char,
    )>,

    pub notationDecl:  UnusedFn,
    pub attributeDecl: UnusedFn,
    pub elementDecl:   UnusedFn,

    pub unparsedEntityDecl: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
        public_id: *const libc::c_char,
        system_id: *const libc::c_char,
        notation_name: *const libc::c_char,
    )>,

    pub setDocumentLocator: UnusedFn,
    pub startDocument:      UnusedFn,
    pub endDocument:        UnusedFn,
    pub startElement:       UnusedFn,
    pub endElement:         UnusedFn,

    pub reference: UnusedFn,

    pub characters: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        ch: *const libc::c_char,
        len: libc::c_int,
    )>,

    pub ignorableWhitespace: UnusedFn,

    pub processingInstruction: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        target: *const libc::c_char,
        data: *const libc::c_char,
    )>,

    pub comment:    UnusedFn,
    pub warning:    UnusedFn,

    pub error:      UnusedFn,

    pub fatalError: UnusedFn,

    pub getParameterEntity: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
    ) -> xmlEntityPtr>,

    pub cdataBlock: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        value: *const libc::c_char,
        len: libc::c_int,
    )>,

    pub externalSubset: UnusedFn,

    pub initialized: libc::c_uint,

    pub _private: gpointer,

    pub startElementNs: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        localname: *mut libc::c_char,
	prefix: *mut libc::c_char,
	uri: *mut libc::c_char,
	nb_namespaces: libc::c_int,
	namespaces: *mut *mut libc::c_char,
	nb_attributes: libc::c_int,
	nb_defaulted: libc::c_int,
	attributes: *mut *mut libc::c_char,
    )>,

    pub endElementNs: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        localname: *mut libc::c_char,
	prefix: *mut libc::c_char,
	uri: *mut libc::c_char,
    )>,

    pub serror: Option<unsafe extern "C" fn(user_data: *mut libc::c_void, error: xmlErrorPtr)>,
}

pub type xmlSAXHandlerPtr = *mut xmlSAXHandler;

// The original struct _xmlParserCtxt in libxml2 has a *ton* of
// fields; mostly are implementation details.  We only require access
// to fields up to replaceEntities, so we'll represent up to that
// field, and ignore subsequent ones.  This struct is used just to
// cast the xmlParserCtxtPtr that we get out of libxml2 into a
// Rust-visible structure; we don't need a complete representation of the
// original struct.
#[repr(C)]
pub struct xmlParserCtxt {
    pub sax: gpointer,
    pub userData: gpointer,
    pub myDoc: xmlDocPtr,
    pub wellFormed: libc::c_int,
    pub replaceEntities: libc::c_int,
    // ... libxml2 has more fields here; we don't use them
}

pub type xmlParserCtxtPtr = *mut xmlParserCtxt;

#[repr(C)]
pub struct xmlError {
    pub domain: libc::c_int,
    pub code: libc::c_int,
    pub message: *const libc::c_char,
    pub level: libc::c_int,
    pub file: *const libc::c_char,
    pub line: libc::c_int,
    pub str1: *const libc::c_char,
    pub str2: *const libc::c_char,
    pub str3: *const libc::c_char,
    pub int1: libc::c_int,
    pub int2: libc::c_int,
    pub ctxt: gpointer,
    pub node: gpointer,
}

pub type xmlErrorPtr = *mut xmlError;

pub type xmlInputReadCallback = Option<
    unsafe extern "C" fn(
        context: *mut libc::c_void,
        buffer: *mut libc::c_char,
        len: libc::c_int,
    ) -> libc::c_int,
>;

pub type xmlInputCloseCallback =
    Option<unsafe extern "C" fn(context: *mut libc::c_void) -> libc::c_int>;

pub type xmlCharEncoding = libc::c_int;

extern "C" {
    pub fn xmlInitParser();

    pub fn xmlCreateIOParserCtxt(
        sax: xmlSAXHandlerPtr,
        user_data: *mut libc::c_void,
        ioread: xmlInputReadCallback,
        ioclose: xmlInputCloseCallback,
        ioctx: *mut libc::c_void,
        enc: xmlCharEncoding,
    ) -> xmlParserCtxtPtr;

    pub fn xmlStopParser(ctxt: xmlParserCtxtPtr);

    pub fn xmlParseDocument(ctxt: xmlParserCtxtPtr) -> libc::c_int;

    pub fn xmlFreeDoc(doc: xmlDocPtr);

    pub fn xmlFreeParserCtxt(ctxt: xmlParserCtxtPtr);

    pub fn xmlCtxtGetLastError(ctxt: *mut libc::c_void) -> xmlErrorPtr;

    pub fn xmlCtxtUseOptions(ctxt: xmlParserCtxtPtr, options: libc::c_int) -> libc::c_int;

    pub fn xmlNewEntity(
        doc: xmlDocPtr,
        name: *const libc::c_char,
        type_: libc::c_int,
        external_id: *const libc::c_char,
        system_id: *const libc::c_char,
        content: *const libc::c_char,
    ) -> xmlEntityPtr;
}
<commit_msg>Allow capitalized acronyms for the libxml2 binding<commit_after>//! Hand-written binding to the very minimal part of libxml2 that we need.

#![allow(clippy::upper_case_acronyms)]
#![allow(non_snake_case, non_camel_case_types)]

use glib_sys::gpointer;

pub const XML_CHAR_ENCODING_NONE: libc::c_int = 0;

pub const XML_INTERNAL_GENERAL_ENTITY: libc::c_int = 1;

pub const XML_PARSE_NONET: libc::c_int = 1 << 11;
pub const XML_PARSE_HUGE: libc::c_int = 1 << 19;
pub const XML_PARSE_BIG_LINES: libc::c_int = 1 << 22;

pub const XML_SAX2_MAGIC: libc::c_uint = 0xDEEDBEAF;

pub type xmlDocPtr = gpointer;

pub type xmlEntityPtr = gpointer;

pub type UnusedFn = Option<unsafe extern "C" fn()>;

#[rustfmt::skip]
#[repr(C)]
pub struct xmlSAXHandler {
    pub internalSubset:    UnusedFn,
    pub isStandalone:      UnusedFn,
    pub hasInternalSubset: UnusedFn,
    pub hasExternalSubset: UnusedFn,
    pub resolveEntity:     UnusedFn,

    pub getEntity: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
    ) -> xmlEntityPtr>,

    pub entityDecl: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
        type_: libc::c_int,
        public_id: *const libc::c_char,
        system_id: *const libc::c_char,
        content: *const libc::c_char,
    )>,

    pub notationDecl:  UnusedFn,
    pub attributeDecl: UnusedFn,
    pub elementDecl:   UnusedFn,

    pub unparsedEntityDecl: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
        public_id: *const libc::c_char,
        system_id: *const libc::c_char,
        notation_name: *const libc::c_char,
    )>,

    pub setDocumentLocator: UnusedFn,
    pub startDocument:      UnusedFn,
    pub endDocument:        UnusedFn,
    pub startElement:       UnusedFn,
    pub endElement:         UnusedFn,

    pub reference: UnusedFn,

    pub characters: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        ch: *const libc::c_char,
        len: libc::c_int,
    )>,

    pub ignorableWhitespace: UnusedFn,

    pub processingInstruction: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        target: *const libc::c_char,
        data: *const libc::c_char,
    )>,

    pub comment:    UnusedFn,
    pub warning:    UnusedFn,

    pub error:      UnusedFn,

    pub fatalError: UnusedFn,

    pub getParameterEntity: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        name: *const libc::c_char,
    ) -> xmlEntityPtr>,

    pub cdataBlock: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        value: *const libc::c_char,
        len: libc::c_int,
    )>,

    pub externalSubset: UnusedFn,

    pub initialized: libc::c_uint,

    pub _private: gpointer,

    pub startElementNs: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        localname: *mut libc::c_char,
	prefix: *mut libc::c_char,
	uri: *mut libc::c_char,
	nb_namespaces: libc::c_int,
	namespaces: *mut *mut libc::c_char,
	nb_attributes: libc::c_int,
	nb_defaulted: libc::c_int,
	attributes: *mut *mut libc::c_char,
    )>,

    pub endElementNs: Option<unsafe extern "C" fn(
        ctx: *mut libc::c_void,
        localname: *mut libc::c_char,
	prefix: *mut libc::c_char,
	uri: *mut libc::c_char,
    )>,

    pub serror: Option<unsafe extern "C" fn(user_data: *mut libc::c_void, error: xmlErrorPtr)>,
}

pub type xmlSAXHandlerPtr = *mut xmlSAXHandler;

// The original struct _xmlParserCtxt in libxml2 has a *ton* of
// fields; mostly are implementation details.  We only require access
// to fields up to replaceEntities, so we'll represent up to that
// field, and ignore subsequent ones.  This struct is used just to
// cast the xmlParserCtxtPtr that we get out of libxml2 into a
// Rust-visible structure; we don't need a complete representation of the
// original struct.
#[repr(C)]
pub struct xmlParserCtxt {
    pub sax: gpointer,
    pub userData: gpointer,
    pub myDoc: xmlDocPtr,
    pub wellFormed: libc::c_int,
    pub replaceEntities: libc::c_int,
    // ... libxml2 has more fields here; we don't use them
}

pub type xmlParserCtxtPtr = *mut xmlParserCtxt;

#[repr(C)]
pub struct xmlError {
    pub domain: libc::c_int,
    pub code: libc::c_int,
    pub message: *const libc::c_char,
    pub level: libc::c_int,
    pub file: *const libc::c_char,
    pub line: libc::c_int,
    pub str1: *const libc::c_char,
    pub str2: *const libc::c_char,
    pub str3: *const libc::c_char,
    pub int1: libc::c_int,
    pub int2: libc::c_int,
    pub ctxt: gpointer,
    pub node: gpointer,
}

pub type xmlErrorPtr = *mut xmlError;

pub type xmlInputReadCallback = Option<
    unsafe extern "C" fn(
        context: *mut libc::c_void,
        buffer: *mut libc::c_char,
        len: libc::c_int,
    ) -> libc::c_int,
>;

pub type xmlInputCloseCallback =
    Option<unsafe extern "C" fn(context: *mut libc::c_void) -> libc::c_int>;

pub type xmlCharEncoding = libc::c_int;

extern "C" {
    pub fn xmlInitParser();

    pub fn xmlCreateIOParserCtxt(
        sax: xmlSAXHandlerPtr,
        user_data: *mut libc::c_void,
        ioread: xmlInputReadCallback,
        ioclose: xmlInputCloseCallback,
        ioctx: *mut libc::c_void,
        enc: xmlCharEncoding,
    ) -> xmlParserCtxtPtr;

    pub fn xmlStopParser(ctxt: xmlParserCtxtPtr);

    pub fn xmlParseDocument(ctxt: xmlParserCtxtPtr) -> libc::c_int;

    pub fn xmlFreeDoc(doc: xmlDocPtr);

    pub fn xmlFreeParserCtxt(ctxt: xmlParserCtxtPtr);

    pub fn xmlCtxtGetLastError(ctxt: *mut libc::c_void) -> xmlErrorPtr;

    pub fn xmlCtxtUseOptions(ctxt: xmlParserCtxtPtr, options: libc::c_int) -> libc::c_int;

    pub fn xmlNewEntity(
        doc: xmlDocPtr,
        name: *const libc::c_char,
        type_: libc::c_int,
        external_id: *const libc::c_char,
        system_id: *const libc::c_char,
        content: *const libc::c_char,
    ) -> xmlEntityPtr;
}
<|endoftext|>


<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.

/// Magic addresses externally used to lay out x86_64 VMs.

/// Initial stack for the boot CPU.
pub const BOOT_STACK_POINTER: u64 = 0x8ff0;

/// Kernel command line start address.
pub const CMDLINE_START: u64 = 0x20000;
/// Kernel command line start address maximum size.
pub const CMDLINE_MAX_SIZE: usize = 0x10000;

/// Start of the high memory.
pub const HIMEM_START: u64 = 0x0010_0000; // 1 MB.

// Typically, on x86 systems 24 IRQs are used (0-23).
/// First usable IRQ ID for virtio device interrupts on x86_64.
pub const IRQ_BASE: u32 = 5;
/// Last usable IRQ ID for virtio device interrupts on x86_64.
pub const IRQ_MAX: u32 = 23;

/// Address for the TSS setup.
pub const KVM_TSS_ADDRESS: u64 = 0xfffb_d000;

/// The 'zero page', a.k.a linux kernel bootparams.
pub const ZERO_PAGE_START: u64 = 0x7000;
<commit_msg>Cmdline max size changed on x86<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.

/// Magic addresses externally used to lay out x86_64 VMs.

/// Initial stack for the boot CPU.
pub const BOOT_STACK_POINTER: u64 = 0x8ff0;

/// Kernel command line start address.
pub const CMDLINE_START: u64 = 0x20000;
/// Kernel command line maximum size.
pub const CMDLINE_MAX_SIZE: usize = 2048;

/// Start of the high memory.
pub const HIMEM_START: u64 = 0x0010_0000; // 1 MB.

// Typically, on x86 systems 24 IRQs are used (0-23).
/// First usable IRQ ID for virtio device interrupts on x86_64.
pub const IRQ_BASE: u32 = 5;
/// Last usable IRQ ID for virtio device interrupts on x86_64.
pub const IRQ_MAX: u32 = 23;

/// Address for the TSS setup.
pub const KVM_TSS_ADDRESS: u64 = 0xfffb_d000;

/// The 'zero page', a.k.a linux kernel bootparams.
pub const ZERO_PAGE_START: u64 = 0x7000;
<|endoftext|>