<commit_before>//! VFS stands for Virtual File System.
//!
//! When doing analysis, we don't want to do any IO, we want to keep all source
//! code in memory. However, the actual source code is stored on disk, so you
//! need to get it into the memory in the first place somehow. VFS is the
//! component which does this.
//!
//! It is also responsible for watching the disk for changes, and for merging
//! editor state (modified, unsaved files) with disk state.
//! TODO: Some LSP clients support watching the disk, so this crate should
//! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
//!
//! VFS is based on a concept of roots: a set of directories on the file system
//! which are watched for changes. Typically, there will be a root for each
//! Cargo package.
mod io;

use std::{
    cmp::Reverse,
    ffi::OsStr,
    fmt, fs, mem,
    path::{Component, Path, PathBuf},
    sync::Arc,
    thread,
};

use crossbeam_channel::Receiver;
use ra_arena::{impl_arena_id, Arena, RawId};
use relative_path::RelativePathBuf;
use rustc_hash::{FxHashMap, FxHashSet};

pub use crate::io::TaskResult as VfsTask;
use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker};

/// `RootFilter` is a predicate that checks if a file can belong to a root. If
/// several filters match a file (nested dirs), the most nested one wins.
pub(crate) struct RootFilter {
    root: PathBuf,
    filter: fn(RootEntry) -> bool,
}

pub(crate) struct RootEntry<'a, 'b> {
    root: &'a Path,
    path: &'b Path,
}

impl RootFilter {
    fn new(root: PathBuf) -> RootFilter {
        RootFilter {
            root,
            filter: default_filter,
        }
    }
    /// Check if this root can contain `path`. NB: even if this returns
    /// true, the `path` might actually be conained in some nested root.
    pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
        if !(self.filter)(RootEntry {
            root: &self.root,
            path,
        }) {
            return None;
        }
        let path = path.strip_prefix(&self.root).ok()?;
        RelativePathBuf::from_path(path).ok()
    }
}

pub(crate) fn default_filter(entry: RootEntry) -> bool {
    if entry.path.is_dir() {
        // first component relative to root is "target"
        entry
            .path
            .strip_prefix(entry.root)
            .map(|p| p.components().next() != Some(Component::Normal(OsStr::new("target"))))
            .unwrap_or(false)
    } else {
        entry.path.extension() == Some(OsStr::new("rs"))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsRoot(pub RawId);
impl_arena_id!(VfsRoot);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsFile(pub RawId);
impl_arena_id!(VfsFile);

struct VfsFileData {
    root: VfsRoot,
    path: RelativePathBuf,
    is_overlayed: bool,
    text: Arc<String>,
}

pub struct Vfs {
    roots: Arena<VfsRoot, Arc<RootFilter>>,
    files: Arena<VfsFile, VfsFileData>,
    root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
    pending_changes: Vec<VfsChange>,
    worker: Worker,
}

impl fmt::Debug for Vfs {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Vfs { ... }")
    }
}

impl Vfs {
    pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
        let worker = io::Worker::start();

        let mut res = Vfs {
            roots: Arena::default(),
            files: Arena::default(),
            root2files: FxHashMap::default(),
            worker,
            pending_changes: Vec::new(),
        };

        // A hack to make nesting work.
        roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
        for (i, path) in roots.iter().enumerate() {
            let root_filter = Arc::new(RootFilter::new(path.clone()));

            let root = res.roots.alloc(root_filter.clone());
            res.root2files.insert(root, Default::default());

            let nested_roots = roots[..i]
                .iter()
                .filter(|it| it.starts_with(path))
                .map(|it| it.clone())
                .collect::<Vec<_>>();

            let task = io::Task::AddRoot {
                root,
                path: path.clone(),
                root_filter,
                nested_roots,
            };
            res.worker.sender().send(task).unwrap();
        }
        let roots = res.roots.iter().map(|(id, _)| id).collect();
        (res, roots)
    }

    pub fn root2path(&self, root: VfsRoot) -> PathBuf {
        self.roots[root].root.clone()
    }

    pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
        if let Some((_root, _path, Some(file))) = self.find_root(path) {
            return Some(file);
        }
        None
    }

    pub fn file2path(&self, file: VfsFile) -> PathBuf {
        let rel_path = &self.files[file].path;
        let root_path = &self.roots[self.files[file].root].root;
        rel_path.to_path(root_path)
    }

    pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
        if let Some((_root, _path, Some(file))) = self.find_root(path) {
            return Some(file);
        }
        None
    }

    pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
        if let Some((root, rel_path, file)) = self.find_root(path) {
            return if let Some(file) = file {
                Some(file)
            } else {
                let text = fs::read_to_string(path).unwrap_or_default();
                let text = Arc::new(text);
                let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
                let change = VfsChange::AddFile {
                    file,
                    text,
                    root,
                    path: rel_path,
                };
                self.pending_changes.push(change);
                Some(file)
            };
        }
        None
    }

    pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
        self.worker.receiver()
    }

    pub fn handle_task(&mut self, task: io::TaskResult) {
        match task {
            TaskResult::AddRoot(task) => {
                let mut files = Vec::new();
                // While we were scanning the root in the backgound, a file might have
                // been open in the editor, so we need to account for that.
                let exising = self.root2files[&task.root]
                    .iter()
                    .map(|&file| (self.files[file].path.clone(), file))
                    .collect::<FxHashMap<_, _>>();
                for (path, text) in task.files {
                    if let Some(&file) = exising.get(&path) {
                        let text = Arc::clone(&self.files[file].text);
                        files.push((file, path, text));
                        continue;
                    }
                    let text = Arc::new(text);
                    let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
                    files.push((file, path, text));
                }

                let change = VfsChange::AddRoot {
                    root: task.root,
                    files,
                };
                self.pending_changes.push(change);
            }
            TaskResult::HandleChange(change) => match &change {
                WatcherChange::Create(path) if path.is_dir() => {
                    if let Some((root, _path, _file)) = self.find_root(&path) {
                        let root_filter = self.roots[root].clone();
                        self.worker
                            .sender()
                            .send(Task::Watch {
                                dir: path.to_path_buf(),
                                root_filter,
                            })
                            .unwrap()
                    }
                }
                WatcherChange::Create(path)
                | WatcherChange::Remove(path)
                | WatcherChange::Write(path) => {
                    if self.should_handle_change(&path) {
                        self.worker.sender().send(Task::LoadChange(change)).unwrap()
                    }
                }
                WatcherChange::Rescan => {
                    // TODO we should reload all files
                }
            },
            TaskResult::LoadChange(change) => match change {
                WatcherChangeData::Create { path, text }
                | WatcherChangeData::Write { path, text } => {
                    if let Some((root, path, file)) = self.find_root(&path) {
                        if let Some(file) = file {
                            self.do_change_file(file, text, false);
                        } else {
                            self.do_add_file(root, path, text, false);
                        }
                    }
                }
                WatcherChangeData::Remove { path } => {
                    if let Some((root, path, file)) = self.find_root(&path) {
                        if let Some(file) = file {
                            self.do_remove_file(root, path, file, false);
                        }
                    }
                }
            },
            TaskResult::NoOp => {}
        }
    }

    fn should_handle_change(&self, path: &Path) -> bool {
        if let Some((_root, _rel_path, file)) = self.find_root(&path) {
            if let Some(file) = file {
                if self.files[file].is_overlayed {
                    // file is overlayed
                    log::debug!("skipping overlayed \"{}\"", path.display());
                    return false;
                }
            }
            true
        } else {
            // file doesn't belong to any root
            false
        }
    }

    fn do_add_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        text: String,
        is_overlay: bool,
    ) -> Option<VfsFile> {
        let text = Arc::new(text);
        let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
        self.pending_changes.push(VfsChange::AddFile {
            file,
            root,
            path,
            text,
        });
        Some(file)
    }

    fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
        if !is_overlay && self.files[file].is_overlayed {
            return;
        }
        let text = Arc::new(text);
        self.change_file(file, text.clone(), is_overlay);
        self.pending_changes
            .push(VfsChange::ChangeFile { file, text });
    }

    fn do_remove_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        file: VfsFile,
        is_overlay: bool,
    ) {
        if !is_overlay && self.files[file].is_overlayed {
            return;
        }
        self.remove_file(file);
        self.pending_changes
            .push(VfsChange::RemoveFile { root, path, file });
    }

    pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
        if let Some((root, rel_path, file)) = self.find_root(path) {
            if let Some(file) = file {
                self.do_change_file(file, text, true);
                Some(file)
            } else {
                self.do_add_file(root, rel_path, text, true)
            }
        } else {
            None
        }
    }

    pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
        if let Some((_root, _path, file)) = self.find_root(path) {
            let file = file.expect("can't change a file which wasn't added");
            self.do_change_file(file, new_text, true);
        }
    }

    pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
        if let Some((root, path, file)) = self.find_root(path) {
            let file = file.expect("can't remove a file which wasn't added");
            let full_path = path.to_path(&self.roots[root].root);
            if let Ok(text) = fs::read_to_string(&full_path) {
                self.do_change_file(file, text, true);
            } else {
                self.do_remove_file(root, path, file, true);
            }
            Some(file)
        } else {
            None
        }
    }

    pub fn commit_changes(&mut self) -> Vec<VfsChange> {
        mem::replace(&mut self.pending_changes, Vec::new())
    }

    /// Sutdown the VFS and terminate the background watching thread.
    pub fn shutdown(self) -> thread::Result<()> {
        self.worker.shutdown()
    }

    fn add_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        text: Arc<String>,
        is_overlayed: bool,
    ) -> VfsFile {
        let data = VfsFileData {
            root,
            path,
            text,
            is_overlayed,
        };
        let file = self.files.alloc(data);
        self.root2files.get_mut(&root).unwrap().insert(file);
        file
    }

    fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
        let mut file_data = &mut self.files[file];
        file_data.text = new_text;
        file_data.is_overlayed = is_overlayed;
    }

    fn remove_file(&mut self, file: VfsFile) {
        //FIXME: use arena with removal
        self.files[file].text = Default::default();
        self.files[file].path = Default::default();
        let root = self.files[file].root;
        let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
        assert!(removed);
    }

    fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
        let (root, path) = self
            .roots
            .iter()
            .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
        let file = self.root2files[&root]
            .iter()
            .map(|&it| it)
            .find(|&file| self.files[file].path == path);
        Some((root, path, file))
    }
}

#[derive(Debug, Clone)]
pub enum VfsChange {
    AddRoot {
        root: VfsRoot,
        files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
    },
    AddFile {
        root: VfsRoot,
        file: VfsFile,
        path: RelativePathBuf,
        text: Arc<String>,
    },
    RemoveFile {
        root: VfsRoot,
        file: VfsFile,
        path: RelativePathBuf,
    },
    ChangeFile {
        file: VfsFile,
        text: Arc<String>,
    },
}
<commit_msg>simplify and optimize `RootFilter`by determining if is contained first<commit_after>//! VFS stands for Virtual File System.
//!
//! When doing analysis, we don't want to do any IO, we want to keep all source
//! code in memory. However, the actual source code is stored on disk, so you
//! need to get it into the memory in the first place somehow. VFS is the
//! component which does this.
//!
//! It is also responsible for watching the disk for changes, and for merging
//! editor state (modified, unsaved files) with disk state.
//! TODO: Some LSP clients support watching the disk, so this crate should
//! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
//!
//! VFS is based on a concept of roots: a set of directories on the file system
//! which are watched for changes. Typically, there will be a root for each
//! Cargo package.
mod io;

use std::{
    cmp::Reverse,
    fmt, fs, mem,
    path::{Path, PathBuf},
    sync::Arc,
    thread,
};

use crossbeam_channel::Receiver;
use ra_arena::{impl_arena_id, Arena, RawId};
use relative_path::{Component, RelativePath, RelativePathBuf};
use rustc_hash::{FxHashMap, FxHashSet};

pub use crate::io::TaskResult as VfsTask;
use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker};

/// `RootFilter` is a predicate that checks if a file can belong to a root. If
/// several filters match a file (nested dirs), the most nested one wins.
pub(crate) struct RootFilter {
    root: PathBuf,
    filter: fn(&Path, &RelativePath) -> bool,
}

impl RootFilter {
    fn new(root: PathBuf) -> RootFilter {
        RootFilter {
            root,
            filter: default_filter,
        }
    }
    /// Check if this root can contain `path`. NB: even if this returns
    /// true, the `path` might actually be conained in some nested root.
    pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
        let rel_path = path.strip_prefix(&self.root).ok()?;
        let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
        if !(self.filter)(path, rel_path.as_relative_path()) {
            return None;
        }
        Some(rel_path)
    }
}

pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
    if path.is_dir() {
        rel_path.components().next() != Some(Component::Normal("target"))
    } else {
        rel_path.extension() == Some("rs")
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsRoot(pub RawId);
impl_arena_id!(VfsRoot);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsFile(pub RawId);
impl_arena_id!(VfsFile);

struct VfsFileData {
    root: VfsRoot,
    path: RelativePathBuf,
    is_overlayed: bool,
    text: Arc<String>,
}

pub struct Vfs {
    roots: Arena<VfsRoot, Arc<RootFilter>>,
    files: Arena<VfsFile, VfsFileData>,
    root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
    pending_changes: Vec<VfsChange>,
    worker: Worker,
}

impl fmt::Debug for Vfs {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Vfs { ... }")
    }
}

impl Vfs {
    pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
        let worker = io::Worker::start();

        let mut res = Vfs {
            roots: Arena::default(),
            files: Arena::default(),
            root2files: FxHashMap::default(),
            worker,
            pending_changes: Vec::new(),
        };

        // A hack to make nesting work.
        roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
        for (i, path) in roots.iter().enumerate() {
            let root_filter = Arc::new(RootFilter::new(path.clone()));

            let root = res.roots.alloc(root_filter.clone());
            res.root2files.insert(root, Default::default());

            let nested_roots = roots[..i]
                .iter()
                .filter(|it| it.starts_with(path))
                .map(|it| it.clone())
                .collect::<Vec<_>>();

            let task = io::Task::AddRoot {
                root,
                path: path.clone(),
                root_filter,
                nested_roots,
            };
            res.worker.sender().send(task).unwrap();
        }
        let roots = res.roots.iter().map(|(id, _)| id).collect();
        (res, roots)
    }

    pub fn root2path(&self, root: VfsRoot) -> PathBuf {
        self.roots[root].root.clone()
    }

    pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
        if let Some((_root, _path, Some(file))) = self.find_root(path) {
            return Some(file);
        }
        None
    }

    pub fn file2path(&self, file: VfsFile) -> PathBuf {
        let rel_path = &self.files[file].path;
        let root_path = &self.roots[self.files[file].root].root;
        rel_path.to_path(root_path)
    }

    pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
        if let Some((_root, _path, Some(file))) = self.find_root(path) {
            return Some(file);
        }
        None
    }

    pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
        if let Some((root, rel_path, file)) = self.find_root(path) {
            return if let Some(file) = file {
                Some(file)
            } else {
                let text = fs::read_to_string(path).unwrap_or_default();
                let text = Arc::new(text);
                let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
                let change = VfsChange::AddFile {
                    file,
                    text,
                    root,
                    path: rel_path,
                };
                self.pending_changes.push(change);
                Some(file)
            };
        }
        None
    }

    pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
        self.worker.receiver()
    }

    pub fn handle_task(&mut self, task: io::TaskResult) {
        match task {
            TaskResult::AddRoot(task) => {
                let mut files = Vec::new();
                // While we were scanning the root in the backgound, a file might have
                // been open in the editor, so we need to account for that.
                let exising = self.root2files[&task.root]
                    .iter()
                    .map(|&file| (self.files[file].path.clone(), file))
                    .collect::<FxHashMap<_, _>>();
                for (path, text) in task.files {
                    if let Some(&file) = exising.get(&path) {
                        let text = Arc::clone(&self.files[file].text);
                        files.push((file, path, text));
                        continue;
                    }
                    let text = Arc::new(text);
                    let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
                    files.push((file, path, text));
                }

                let change = VfsChange::AddRoot {
                    root: task.root,
                    files,
                };
                self.pending_changes.push(change);
            }
            TaskResult::HandleChange(change) => match &change {
                WatcherChange::Create(path) if path.is_dir() => {
                    if let Some((root, _path, _file)) = self.find_root(&path) {
                        let root_filter = self.roots[root].clone();
                        self.worker
                            .sender()
                            .send(Task::Watch {
                                dir: path.to_path_buf(),
                                root_filter,
                            })
                            .unwrap()
                    }
                }
                WatcherChange::Create(path)
                | WatcherChange::Remove(path)
                | WatcherChange::Write(path) => {
                    if self.should_handle_change(&path) {
                        self.worker.sender().send(Task::LoadChange(change)).unwrap()
                    }
                }
                WatcherChange::Rescan => {
                    // TODO we should reload all files
                }
            },
            TaskResult::LoadChange(change) => match change {
                WatcherChangeData::Create { path, text }
                | WatcherChangeData::Write { path, text } => {
                    if let Some((root, path, file)) = self.find_root(&path) {
                        if let Some(file) = file {
                            self.do_change_file(file, text, false);
                        } else {
                            self.do_add_file(root, path, text, false);
                        }
                    }
                }
                WatcherChangeData::Remove { path } => {
                    if let Some((root, path, file)) = self.find_root(&path) {
                        if let Some(file) = file {
                            self.do_remove_file(root, path, file, false);
                        }
                    }
                }
            },
            TaskResult::NoOp => {}
        }
    }

    fn should_handle_change(&self, path: &Path) -> bool {
        if let Some((_root, _rel_path, file)) = self.find_root(&path) {
            if let Some(file) = file {
                if self.files[file].is_overlayed {
                    // file is overlayed
                    log::debug!("skipping overlayed \"{}\"", path.display());
                    return false;
                }
            }
            true
        } else {
            // file doesn't belong to any root
            false
        }
    }

    fn do_add_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        text: String,
        is_overlay: bool,
    ) -> Option<VfsFile> {
        let text = Arc::new(text);
        let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
        self.pending_changes.push(VfsChange::AddFile {
            file,
            root,
            path,
            text,
        });
        Some(file)
    }

    fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
        if !is_overlay && self.files[file].is_overlayed {
            return;
        }
        let text = Arc::new(text);
        self.change_file(file, text.clone(), is_overlay);
        self.pending_changes
            .push(VfsChange::ChangeFile { file, text });
    }

    fn do_remove_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        file: VfsFile,
        is_overlay: bool,
    ) {
        if !is_overlay && self.files[file].is_overlayed {
            return;
        }
        self.remove_file(file);
        self.pending_changes
            .push(VfsChange::RemoveFile { root, path, file });
    }

    pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
        if let Some((root, rel_path, file)) = self.find_root(path) {
            if let Some(file) = file {
                self.do_change_file(file, text, true);
                Some(file)
            } else {
                self.do_add_file(root, rel_path, text, true)
            }
        } else {
            None
        }
    }

    pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
        if let Some((_root, _path, file)) = self.find_root(path) {
            let file = file.expect("can't change a file which wasn't added");
            self.do_change_file(file, new_text, true);
        }
    }

    pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
        if let Some((root, path, file)) = self.find_root(path) {
            let file = file.expect("can't remove a file which wasn't added");
            let full_path = path.to_path(&self.roots[root].root);
            if let Ok(text) = fs::read_to_string(&full_path) {
                self.do_change_file(file, text, true);
            } else {
                self.do_remove_file(root, path, file, true);
            }
            Some(file)
        } else {
            None
        }
    }

    pub fn commit_changes(&mut self) -> Vec<VfsChange> {
        mem::replace(&mut self.pending_changes, Vec::new())
    }

    /// Sutdown the VFS and terminate the background watching thread.
    pub fn shutdown(self) -> thread::Result<()> {
        self.worker.shutdown()
    }

    fn add_file(
        &mut self,
        root: VfsRoot,
        path: RelativePathBuf,
        text: Arc<String>,
        is_overlayed: bool,
    ) -> VfsFile {
        let data = VfsFileData {
            root,
            path,
            text,
            is_overlayed,
        };
        let file = self.files.alloc(data);
        self.root2files.get_mut(&root).unwrap().insert(file);
        file
    }

    fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
        let mut file_data = &mut self.files[file];
        file_data.text = new_text;
        file_data.is_overlayed = is_overlayed;
    }

    fn remove_file(&mut self, file: VfsFile) {
        //FIXME: use arena with removal
        self.files[file].text = Default::default();
        self.files[file].path = Default::default();
        let root = self.files[file].root;
        let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
        assert!(removed);
    }

    fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
        let (root, path) = self
            .roots
            .iter()
            .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
        let file = self.root2files[&root]
            .iter()
            .map(|&it| it)
            .find(|&file| self.files[file].path == path);
        Some((root, path, file))
    }
}

#[derive(Debug, Clone)]
pub enum VfsChange {
    AddRoot {
        root: VfsRoot,
        files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
    },
    AddFile {
        root: VfsRoot,
        file: VfsFile,
        path: RelativePathBuf,
        text: Arc<String>,
    },
    RemoveFile {
        root: VfsRoot,
        file: VfsFile,
        path: RelativePathBuf,
    },
    ChangeFile {
        file: VfsFile,
        text: Arc<String>,
    },
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use std::{
    hash::{Hash, Hasher},
    iter::successors,
    marker::PhantomData,
};

use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange};

/// A pointer to a syntax node inside a file. It can be used to remember a
/// specific node across reparses of the same file.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SyntaxNodePtr {
    pub(crate) range: TextRange,
    kind: SyntaxKind,
}

impl SyntaxNodePtr {
    pub fn new(node: &SyntaxNode) -> SyntaxNodePtr {
        SyntaxNodePtr { range: node.text_range(), kind: node.kind() }
    }

    pub fn to_node(&self, root: &SyntaxNode) -> SyntaxNode {
        assert!(root.parent().is_none());
        successors(Some(root.clone()), |node| {
            node.children().find(|it| it.text_range().contains_range(self.range))
        })
        .find(|it| it.text_range() == self.range && it.kind() == self.kind)
        .unwrap_or_else(|| panic!("can't resolve local ptr to SyntaxNode: {:?}", self))
    }

    pub fn cast<N: AstNode>(self) -> Option<AstPtr<N>> {
        if !N::can_cast(self.kind) {
            return None;
        }
        Some(AstPtr { raw: self, _ty: PhantomData })
    }
}

/// Like `SyntaxNodePtr`, but remembers the type of node
#[derive(Debug)]
pub struct AstPtr<N: AstNode> {
    raw: SyntaxNodePtr,
    _ty: PhantomData<fn() -> N>,
}

impl<N: AstNode> Clone for AstPtr<N> {
    fn clone(&self) -> AstPtr<N> {
        AstPtr { raw: self.raw.clone(), _ty: PhantomData }
    }
}

impl<N: AstNode> Eq for AstPtr<N> {}

impl<N: AstNode> PartialEq for AstPtr<N> {
    fn eq(&self, other: &AstPtr<N>) -> bool {
        self.raw == other.raw
    }
}

impl<N: AstNode> Hash for AstPtr<N> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.raw.hash(state)
    }
}

impl<N: AstNode> AstPtr<N> {
    pub fn new(node: &N) -> AstPtr<N> {
        AstPtr { raw: SyntaxNodePtr::new(node.syntax()), _ty: PhantomData }
    }

    pub fn to_node(&self, root: &SyntaxNode) -> N {
        let syntax_node = self.raw.to_node(root);
        N::cast(syntax_node).unwrap()
    }

    pub fn syntax_node_ptr(&self) -> SyntaxNodePtr {
        self.raw.clone()
    }

    pub fn cast<U: AstNode>(self) -> Option<AstPtr<U>> {
        if !U::can_cast(self.raw.kind) {
            return None;
        }
        Some(AstPtr { raw: self.raw, _ty: PhantomData })
    }
}

impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
    fn from(ptr: AstPtr<N>) -> SyntaxNodePtr {
        ptr.raw
    }
}

#[test]
fn test_local_syntax_ptr() {
    use crate::{ast, AstNode, SourceFile};

    let file = SourceFile::parse("struct Foo { f: u32, }").ok().unwrap();
    let field = file.syntax().descendants().find_map(ast::RecordField::cast).unwrap();
    let ptr = SyntaxNodePtr::new(field.syntax());
    let field_syntax = ptr.to_node(file.syntax());
    assert_eq!(field.syntax(), &field_syntax);
}
<commit_msg>Document privacy invariant of SyntaxPtr<commit_after>//! FIXME: write short doc here

use std::{
    hash::{Hash, Hasher},
    iter::successors,
    marker::PhantomData,
};

use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange};

/// A pointer to a syntax node inside a file. It can be used to remember a
/// specific node across reparses of the same file.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SyntaxNodePtr {
    // Don't expose this field further. At some point, we might want to replace
    // range with node id.
    pub(crate) range: TextRange,
    kind: SyntaxKind,
}

impl SyntaxNodePtr {
    pub fn new(node: &SyntaxNode) -> SyntaxNodePtr {
        SyntaxNodePtr { range: node.text_range(), kind: node.kind() }
    }

    pub fn to_node(&self, root: &SyntaxNode) -> SyntaxNode {
        assert!(root.parent().is_none());
        successors(Some(root.clone()), |node| {
            node.children().find(|it| it.text_range().contains_range(self.range))
        })
        .find(|it| it.text_range() == self.range && it.kind() == self.kind)
        .unwrap_or_else(|| panic!("can't resolve local ptr to SyntaxNode: {:?}", self))
    }

    pub fn cast<N: AstNode>(self) -> Option<AstPtr<N>> {
        if !N::can_cast(self.kind) {
            return None;
        }
        Some(AstPtr { raw: self, _ty: PhantomData })
    }
}

/// Like `SyntaxNodePtr`, but remembers the type of node
#[derive(Debug)]
pub struct AstPtr<N: AstNode> {
    raw: SyntaxNodePtr,
    _ty: PhantomData<fn() -> N>,
}

impl<N: AstNode> Clone for AstPtr<N> {
    fn clone(&self) -> AstPtr<N> {
        AstPtr { raw: self.raw.clone(), _ty: PhantomData }
    }
}

impl<N: AstNode> Eq for AstPtr<N> {}

impl<N: AstNode> PartialEq for AstPtr<N> {
    fn eq(&self, other: &AstPtr<N>) -> bool {
        self.raw == other.raw
    }
}

impl<N: AstNode> Hash for AstPtr<N> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.raw.hash(state)
    }
}

impl<N: AstNode> AstPtr<N> {
    pub fn new(node: &N) -> AstPtr<N> {
        AstPtr { raw: SyntaxNodePtr::new(node.syntax()), _ty: PhantomData }
    }

    pub fn to_node(&self, root: &SyntaxNode) -> N {
        let syntax_node = self.raw.to_node(root);
        N::cast(syntax_node).unwrap()
    }

    pub fn syntax_node_ptr(&self) -> SyntaxNodePtr {
        self.raw.clone()
    }

    pub fn cast<U: AstNode>(self) -> Option<AstPtr<U>> {
        if !U::can_cast(self.raw.kind) {
            return None;
        }
        Some(AstPtr { raw: self.raw, _ty: PhantomData })
    }
}

impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
    fn from(ptr: AstPtr<N>) -> SyntaxNodePtr {
        ptr.raw
    }
}

#[test]
fn test_local_syntax_ptr() {
    use crate::{ast, AstNode, SourceFile};

    let file = SourceFile::parse("struct Foo { f: u32, }").ok().unwrap();
    let field = file.syntax().descendants().find_map(ast::RecordField::cast).unwrap();
    let ptr = SyntaxNodePtr::new(field.syntax());
    let field_syntax = ptr.to_node(file.syntax());
    assert_eq!(field.syntax(), &field_syntax);
}
<|endoftext|>


<commit_before>use crate::conn;
use crate::ui::UI;
use libtiny_common::ChanName;
use libtiny_tui::test_utils::expect_screen;
use libtiny_tui::TUI;
use libtiny_wire::{Cmd, Msg, MsgTarget, Pfx};
use term_input;
use termbox_simple::CellBuf;

use libtiny_client as client;
use term_input as input;

use std::future::Future;
use std::panic::Location;

use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;

struct TestClient {
    nick: String,
}

impl conn::Client for TestClient {
    fn get_serv_name(&self) -> &str {
        SERV_NAME
    }

    fn get_nick(&self) -> String {
        self.nick.clone()
    }

    fn is_nick_accepted(&self) -> bool {
        true
    }
}

static SERV_NAME: &str = "x.y.z";
const DEFAULT_TUI_WIDTH: u16 = 40;
const DEFAULT_TUI_HEIGHT: u16 = 5;

struct TestSetup {
    /// TUI test instance
    tui: TUI,
    /// Send input events to the TUI using this channel
    snd_input_ev: mpsc::Sender<input::Event>,
    /// Send connection events to connection handler (`conn::task`) using this channel
    snd_conn_ev: mpsc::Sender<client::Event>,
}

fn run_test<F, Fut>(nick: String, test: F)
where
    F: Fn(TestSetup) -> Fut,
    Fut: Future<Output = ()>,
{
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    let local = tokio::task::LocalSet::new();

    local.block_on(&runtime, async move {
        // Create test TUI
        let (snd_input_ev, rcv_input_ev) = mpsc::channel::<term_input::Event>(100);
        let rcv_input_ev = ReceiverStream::new(rcv_input_ev);
        let (tui, _rcv_tui_ev) = TUI::run_test(
            DEFAULT_TUI_WIDTH,
            DEFAULT_TUI_HEIGHT,
            rcv_input_ev.map(|ev| Ok(ev)),
        );

        let tiny_ui = UI::new(tui.clone(), None);

        // Create test connection event channel
        let (snd_conn_ev, rcv_conn_ev) = mpsc::channel::<client::Event>(100);

        // Spawn connection event handler task
        tokio::task::spawn_local(conn::task(
            rcv_conn_ev,
            tiny_ui,
            Box::new(TestClient { nick }),
        ));

        tui.new_server_tab(SERV_NAME, None);
        tui.draw();

        test(TestSetup {
            tui,
            snd_input_ev,
            snd_conn_ev,
        })
        .await;
    });
}

#[test]
fn test_privmsg_from_user_without_user_or_host_part_issue_247() {
    run_test(
        "osa1".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1".to_owned(),
                })
                .await
                .unwrap();
            yield_(5).await;

            // Join a channel to test msg sent to channel
            let join = Msg {
                pfx: Some(Pfx::User {
                    nick: "osa1".to_owned(),
                    user: "a@b".to_owned(),
                }),
                cmd: Cmd::JOIN {
                    chan: ChanName::new("#chan".to_owned()),
                },
            };
            snd_conn_ev.send(client::Event::Msg(join)).await.unwrap();
            yield_(5).await;

            // Send a PRIVMSG to the channel
            let chan_msg = Msg {
                pfx: Some(Pfx::Ambiguous("blah".to_owned())),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::Chan(ChanName::new("#chan".to_owned())),
                    msg: "msg to chan".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };
            snd_conn_ev
                .send(client::Event::Msg(chan_msg))
                .await
                .unwrap();
            yield_(5).await;

            // Send a PRIVMSG to current nick
            let msg = Msg {
                pfx: Some(Pfx::Ambiguous("blah".to_owned())),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::User("osa1".to_owned()),
                    msg: "msg to user".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };
            snd_conn_ev.send(client::Event::Msg(msg)).await.unwrap();
            yield_(5).await;

            // Check channel tab
            next_tab(&snd_input_ev).await; // server tab
            next_tab(&snd_input_ev).await; // channel tab
            yield_(5).await;
            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 blah: msg to chan                 |
             |osa1:                                   |
             |mentions x.y.z #chan blah               |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );

            // Check privmsg tab
            next_tab(&snd_input_ev).await; // privmsg tab
            yield_(5).await;
            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 blah: msg to user                 |
             |osa1:                                   |
             |mentions x.y.z #chan blah               |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

#[test]
fn test_bouncer_relay_issue_271() {
    run_test(
        "osa1-soju".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1-soju".to_owned(),
                })
                .await
                .unwrap();

            let msg = Msg {
                pfx: Some(Pfx::User {
                    nick: "osa1-soju".to_owned(),
                    user: "osa1-soju@127.0.0.1".to_owned(),
                }),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::User("osa1/oftc".to_owned()),
                    msg: "blah blah".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };

            snd_conn_ev.send(client::Event::Msg(msg)).await.unwrap();

            yield_(5).await;
            tui.draw();

            next_tab(&snd_input_ev).await; // server tab
            next_tab(&snd_input_ev).await; // privmsg tab
            yield_(5).await;

            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 osa1-soju: blah blah              |
             |osa1-soju:                              |
             |mentions x.y.z osa1/oftc                |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

#[test]
fn test_privmsg_targetmask_issue_278() {
    run_test(
        "osa1".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            next_tab(&snd_input_ev).await;
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1".to_owned(),
                })
                .await
                .unwrap();

            snd_conn_ev
                .send(client::Event::Msg(Msg {
                    pfx: Some(Pfx::User {
                        nick: "e".to_owned(),
                        user: "e@a/b/c.d".to_owned(),
                    }),
                    cmd: Cmd::PRIVMSG {
                        target: MsgTarget::User("$$*".to_owned()),
                        msg: "blah blah blah".to_owned(),
                        is_notice: true,
                        ctcp: None,
                    },
                }))
                .await
                .unwrap();

            yield_(3).await;

            next_tab(&snd_input_ev).await;

            tui.draw();

            yield_(3).await;

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 e: blah blah blah                 |
             |osa1:                                   |
             |mentions x.y.z e                        |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

async fn next_tab(snd_input_ev: &mpsc::Sender<input::Event>) {
    snd_input_ev
        .send(term_input::Event::Key(term_input::Key::Ctrl('n')))
        .await
        .unwrap();
}

async fn yield_(n: usize) {
    for _ in 0..n {
        tokio::task::yield_now().await;
    }
}

/// Makes all timestamps 00:00
fn normalize_timestamps(cells: &mut CellBuf, w: u16, h: u16) {
    let cells = &mut cells.cells;
    for y in 0..h {
        let x = (w * y) as usize;
        if cells[x].ch.is_ascii_digit()
            && cells[x + 1].ch.is_ascii_digit()
            && cells[x + 2].ch == ':'
            && cells[x + 3].ch.is_ascii_digit()
            && cells[x + 4].ch.is_ascii_digit()
        {
            cells[x].ch = '0';
            cells[x + 1].ch = '0';
            cells[x + 2].ch = ':';
            cells[x + 3].ch = '0';
            cells[x + 4].ch = '0';
        }
    }
}
<commit_msg>Update messages in some of the tests to avoid confusion<commit_after>use crate::conn;
use crate::ui::UI;
use libtiny_common::ChanName;
use libtiny_tui::test_utils::expect_screen;
use libtiny_tui::TUI;
use libtiny_wire::{Cmd, Msg, MsgTarget, Pfx};
use term_input;
use termbox_simple::CellBuf;

use libtiny_client as client;
use term_input as input;

use std::future::Future;
use std::panic::Location;

use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;

struct TestClient {
    nick: String,
}

impl conn::Client for TestClient {
    fn get_serv_name(&self) -> &str {
        SERV_NAME
    }

    fn get_nick(&self) -> String {
        self.nick.clone()
    }

    fn is_nick_accepted(&self) -> bool {
        true
    }
}

static SERV_NAME: &str = "x.y.z";
const DEFAULT_TUI_WIDTH: u16 = 40;
const DEFAULT_TUI_HEIGHT: u16 = 5;

struct TestSetup {
    /// TUI test instance
    tui: TUI,
    /// Send input events to the TUI using this channel
    snd_input_ev: mpsc::Sender<input::Event>,
    /// Send connection events to connection handler (`conn::task`) using this channel
    snd_conn_ev: mpsc::Sender<client::Event>,
}

fn run_test<F, Fut>(nick: String, test: F)
where
    F: Fn(TestSetup) -> Fut,
    Fut: Future<Output = ()>,
{
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    let local = tokio::task::LocalSet::new();

    local.block_on(&runtime, async move {
        // Create test TUI
        let (snd_input_ev, rcv_input_ev) = mpsc::channel::<term_input::Event>(100);
        let rcv_input_ev = ReceiverStream::new(rcv_input_ev);
        let (tui, _rcv_tui_ev) = TUI::run_test(
            DEFAULT_TUI_WIDTH,
            DEFAULT_TUI_HEIGHT,
            rcv_input_ev.map(|ev| Ok(ev)),
        );

        let tiny_ui = UI::new(tui.clone(), None);

        // Create test connection event channel
        let (snd_conn_ev, rcv_conn_ev) = mpsc::channel::<client::Event>(100);

        // Spawn connection event handler task
        tokio::task::spawn_local(conn::task(
            rcv_conn_ev,
            tiny_ui,
            Box::new(TestClient { nick }),
        ));

        tui.new_server_tab(SERV_NAME, None);
        tui.draw();

        test(TestSetup {
            tui,
            snd_input_ev,
            snd_conn_ev,
        })
        .await;
    });
}

#[test]
fn test_privmsg_from_user_without_user_or_host_part_issue_247() {
    run_test(
        "osa1".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1".to_owned(),
                })
                .await
                .unwrap();
            yield_(5).await;

            // Join a channel to test msg sent to channel
            let join = Msg {
                pfx: Some(Pfx::User {
                    nick: "osa1".to_owned(),
                    user: "a@b".to_owned(),
                }),
                cmd: Cmd::JOIN {
                    chan: ChanName::new("#chan".to_owned()),
                },
            };
            snd_conn_ev.send(client::Event::Msg(join)).await.unwrap();
            yield_(5).await;

            // Send a PRIVMSG to the channel
            let chan_msg = Msg {
                pfx: Some(Pfx::Ambiguous("tiny_test_user".to_owned())),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::Chan(ChanName::new("#chan".to_owned())),
                    msg: "msg to chan".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };
            snd_conn_ev
                .send(client::Event::Msg(chan_msg))
                .await
                .unwrap();
            yield_(5).await;

            // Send a PRIVMSG to current nick
            let msg = Msg {
                pfx: Some(Pfx::Ambiguous("tiny_test_user".to_owned())),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::User("osa1".to_owned()),
                    // This generates a notification when the test is run with
                    // desktop-notifications feature, so show a helpful message to not confuse
                    // users (#371)
                    msg: "this is a test in tiny IRC client -- please ignore".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };
            snd_conn_ev.send(client::Event::Msg(msg)).await.unwrap();
            yield_(5).await;

            // Check channel tab
            next_tab(&snd_input_ev).await; // server tab
            next_tab(&snd_input_ev).await; // channel tab
            yield_(5).await;
            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 tiny_test_user: msg to chan       |
             |osa1:                                   |
             |mentions x.y.z #chan tiny_test_user     |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );

            // Check privmsg tab
            next_tab(&snd_input_ev).await; // privmsg tab
            yield_(5).await;
            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |00:00 tiny_test_user: this is a test in |
             |tiny IRC client -- please ignore        |
             |osa1:                                   |
             |mentions x.y.z #chan tiny_test_user     |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

#[test]
fn test_bouncer_relay_issue_271() {
    run_test(
        "osa1-soju".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1-soju".to_owned(),
                })
                .await
                .unwrap();

            let msg = Msg {
                pfx: Some(Pfx::User {
                    nick: "osa1-soju".to_owned(),
                    user: "osa1-soju@127.0.0.1".to_owned(),
                }),
                cmd: Cmd::PRIVMSG {
                    target: MsgTarget::User("osa1/oftc".to_owned()),
                    msg: "blah blah".to_owned(),
                    is_notice: false,
                    ctcp: None,
                },
            };

            snd_conn_ev.send(client::Event::Msg(msg)).await.unwrap();

            yield_(5).await;
            tui.draw();

            next_tab(&snd_input_ev).await; // server tab
            next_tab(&snd_input_ev).await; // privmsg tab
            yield_(5).await;

            tui.draw();

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |                                        |
             |00:00 osa1-soju: blah blah              |
             |osa1-soju:                              |
             |mentions x.y.z osa1/oftc                |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

#[test]
fn test_privmsg_targetmask_issue_278() {
    run_test(
        "osa1".to_owned(),
        |TestSetup {
             tui,
             snd_input_ev,
             snd_conn_ev,
         }| async move {
            next_tab(&snd_input_ev).await;
            snd_conn_ev.send(client::Event::Connected).await.unwrap();
            snd_conn_ev
                .send(client::Event::NickChange {
                    new_nick: "osa1".to_owned(),
                })
                .await
                .unwrap();

            snd_conn_ev
                .send(client::Event::Msg(Msg {
                    pfx: Some(Pfx::User {
                        nick: "tiny_test_user".to_owned(),
                        user: "e@a/b/c.d".to_owned(),
                    }),
                    cmd: Cmd::PRIVMSG {
                        target: MsgTarget::User("$$*".to_owned()),
                        // This generates a notification when the test is run with
                        // desktop-notifications feature, so show a helpful message to not confuse
                        // users (#371)
                        msg: "this is a test in tiny IRC client -- please ignore".to_owned(),
                        is_notice: true,
                        ctcp: None,
                    },
                }))
                .await
                .unwrap();

            yield_(3).await;

            next_tab(&snd_input_ev).await;

            tui.draw();

            yield_(3).await;

            #[rustfmt::skip]
            let screen =
            "|                                        |
             |00:00 tiny_test_user: this is a test in |
             |tiny IRC client -- please ignore        |
             |osa1:                                   |
             |mentions x.y.z tiny_test_user           |";

            let mut front_buffer = tui.get_front_buffer();
            normalize_timestamps(&mut front_buffer, DEFAULT_TUI_WIDTH, DEFAULT_TUI_HEIGHT);
            expect_screen(
                screen,
                &front_buffer,
                DEFAULT_TUI_WIDTH,
                DEFAULT_TUI_HEIGHT,
                Location::caller(),
            );
        },
    )
}

async fn next_tab(snd_input_ev: &mpsc::Sender<input::Event>) {
    snd_input_ev
        .send(term_input::Event::Key(term_input::Key::Ctrl('n')))
        .await
        .unwrap();
}

async fn yield_(n: usize) {
    for _ in 0..n {
        tokio::task::yield_now().await;
    }
}

/// Makes all timestamps 00:00
fn normalize_timestamps(cells: &mut CellBuf, w: u16, h: u16) {
    let cells = &mut cells.cells;
    for y in 0..h {
        let x = (w * y) as usize;
        if cells[x].ch.is_ascii_digit()
            && cells[x + 1].ch.is_ascii_digit()
            && cells[x + 2].ch == ':'
            && cells[x + 3].ch.is_ascii_digit()
            && cells[x + 4].ch.is_ascii_digit()
        {
            cells[x].ch = '0';
            cells[x + 1].ch = '0';
            cells[x + 2].ch = ':';
            cells[x + 3].ch = '0';
            cells[x + 4].ch = '0';
        }
    }
}
<|endoftext|>


<commit_before>#![feature(std_misc)]
mod models;
mod parser;

use std::fs::{File, read_dir};
use std::io::{BufReader, BufRead, Read, Write};
use std::path::{Path};
use std::collections::HashMap;
use std::collections::hash_map::Entry::*;
use std::ascii::OwnedAsciiExt;
use models::*;

fn visit_tokens<T: Read, F: FnMut(String) -> ()>(reader: BufReader<T>, mut counter: F) {
    for line in reader.lines() {
    	if let Ok(line) = line {
	    	for word in line.split(|c: char| !c.is_alphabetic()) {
	    		if word.len() > 0 {
	    			let word = word.to_string().into_ascii_lowercase();
		    		counter(word);
		    	}
	    	}
	    }
    }
}

fn find_most_common_words(corpus_loc: &str, outfile: &str) {
    let path = Path::new(corpus_loc);
    let mut word_counts = HashMap::new();
    visit_files(path, &mut |read| visit_tokens(read, &mut |s|
        match (&mut word_counts).entry(s) {
            Vacant(e) => { e.insert(1); },
            Occupied(mut e) => { *e.get_mut() += 1; },
    }));

    let mut counts: Vec<_> = word_counts.into_iter().collect();
    counts.sort_by(|a, b| b.1.cmp(&a.1));

    let mut out = File::create(&Path::new(outfile)).unwrap();
    for &(ref word, count) in &counts[..] {
        out.write_all(format!("{}, {}\n", word, count).as_bytes()).unwrap();
    }

}

fn load_most_common_words(filename: &str) -> Vec<String> {
    let file = File::open(&Path::new(filename)).unwrap();
    let reader = BufReader::new(file);
    reader.lines().map(|line|
        line.unwrap().split(',').next().unwrap().to_string()
    ).collect()
}

fn visit_files<F: FnMut(BufReader<File>) -> ()>(path: &Path, mut file_processor: F) {
    let files = read_dir(path);
    for file in files.unwrap() {
        let path = file.unwrap().path();
        if path.to_str().unwrap().ends_with(".txt") {
            let file = File::open(&path).unwrap();
            let reader = BufReader::new(file);
            file_processor(reader);
        }
    }
}

fn main() {
    const CORPUS_DIR: &'static str = "/home/jamougha/corpus";
    const WORDS: &'static str = "/home/jamougha/corpus/word_counts.csv";
    //find_most_common_words(CORPUS_DIR, WORDS);
    let words = load_most_common_words(WORDS);
    let mut builder = LanguageModelBuilder::new(words);

    let path = Path::new(CORPUS_DIR);

    visit_files(&path, |read| {
        let mut acc = (&mut builder).new_file();
        visit_tokens(read, |w| (&mut acc).add_word(w));
    });

    let model = builder.build();

    println!("{:?}", &model.nearest_words(model.get("linux").unwrap())[0..20]);

}
<commit_msg>use real corpus and a better word<commit_after>#![feature(std_misc)]
mod models;
mod parser;

use std::fs::{File, read_dir};
use std::io::{BufReader, BufRead, Read, Write};
use std::path::{Path};
use std::collections::HashMap;
use std::collections::hash_map::Entry::*;
use std::ascii::OwnedAsciiExt;
use models::*;

fn visit_tokens<T: Read, F: FnMut(String) -> ()>(reader: BufReader<T>, mut counter: F) {
    for line in reader.lines() {
    	if let Ok(line) = line {
	    	for word in line.split(|c: char| !c.is_alphabetic()) {
	    		if word.len() > 0 {
	    			let word = word.to_string().into_ascii_lowercase();
		    		counter(word);
		    	}
	    	}
	    }
    }
}

fn find_most_common_words(corpus_loc: &str, outfile: &str) {
    let path = Path::new(corpus_loc);
    let mut word_counts = HashMap::new();
    visit_files(path, &mut |read| visit_tokens(read, &mut |s|
        match (&mut word_counts).entry(s) {
            Vacant(e) => { e.insert(1); },
            Occupied(mut e) => { *e.get_mut() += 1; },
    }));

    let mut counts: Vec<_> = word_counts.into_iter().collect();
    counts.sort_by(|a, b| b.1.cmp(&a.1));

    let mut out = File::create(&Path::new(outfile)).unwrap();
    for &(ref word, count) in &counts[..] {
        out.write_all(format!("{}, {}\n", word, count).as_bytes()).unwrap();
    }

}

fn load_most_common_words(filename: &str) -> Vec<String> {
    let file = File::open(&Path::new(filename)).unwrap();
    let reader = BufReader::new(file);
    reader.lines().map(|line|
        line.unwrap().split(',').next().unwrap().to_string()
    ).collect()
}

fn visit_files<F: FnMut(BufReader<File>) -> ()>(path: &Path, mut file_processor: F) {
    let files = read_dir(path);
    for file in files.unwrap() {
        let path = file.unwrap().path();
        if path.to_str().unwrap().ends_with(".txt") {
            let file = File::open(&path).unwrap();
            let reader = BufReader::new(file);
            file_processor(reader);
        }
    }
}

fn main() {
    const CORPUS_DIR: &'static str = "/home/jamougha/corpus/pg";
    const WORDS: &'static str = "/home/jamougha/corpus/pg/word_counts.csv";
    //find_most_common_words(CORPUS_DIR, WORDS);
    let words = load_most_common_words(WORDS);
    let mut builder = LanguageModelBuilder::new(words);

    let path = Path::new(CORPUS_DIR);

    visit_files(&path, |read| {
        let mut acc = (&mut builder).new_file();
        visit_tokens(read, |w| (&mut acc).add_word(w));
    });

    let model = builder.build();

    println!("{:?}", &model.nearest_words(model.get("king").unwrap())[0..20]);

}
<|endoftext|>


<commit_before>use std::env;
use std::path::PathBuf;
use std::process;
use std::{cell::RefCell, str::FromStr};

use getopts::Options;
use hprof::Profiler;
use libgir::{self as gir, Config, Library, WorkMode};

fn print_usage(program: &str, opts: Options) {
    let brief = format!(
        "Usage: {program} [options] [<library> <version>]
       {program} (-h | --help)",
        program = program
    );
    print!("{}", opts.usage(&brief));
}

trait OptionStr {
    fn as_str_ref(&self) -> Option<&str>;
}

impl<S: AsRef<str>> OptionStr for Option<S> {
    fn as_str_ref(&self) -> Option<&str> {
        self.as_ref().map(|string| string.as_ref())
    }
}

#[allow(clippy::large_enum_variant)]
enum RunKind {
    Config(Config),
    CheckGirFile(String),
}

fn build_config() -> Result<RunKind, String> {
    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();

    let mut options = Options::new();
    options.optopt(
        "c",
        "config",
        "Config file path (default: Gir.toml)",
        "CONFIG",
    );
    options.optflag("h", "help", "Show this message");
    options.optopt("d", "gir-directory", "Directory for girs", "GIRSPATH");
    options.optopt(
        "m",
        "mode",
        "Work mode: doc, normal, sys or not_bound",
        "MODE",
    );
    options.optopt("o", "target", "Target path", "PATH");
    options.optopt("p", "doc-target-path", "Doc target path", "PATH");
    options.optflag("b", "make-backup", "Make backup before generating");
    options.optflag("s", "stats", "Show statistics");
    options.optflag("", "disable-format", "Disable formatting generated code");
    options.optopt(
        "",
        "check-gir-file",
        "Check if the given `.gir` file is valid",
        "PATH",
    );

    let matches = options.parse(&args[1..]).map_err(|e| e.to_string())?;

    if let Some(check_gir_file) = matches.opt_str("check-gir-file") {
        return Ok(RunKind::CheckGirFile(check_gir_file));
    }

    if matches.opt_present("h") {
        print_usage(&program, options);
        process::exit(0);
    }

    let work_mode = match matches.opt_str("m") {
        None => None,
        Some(s) => match WorkMode::from_str(&s) {
            Ok(w) => Some(w),
            Err(e) => {
                eprintln!("Error (switching to default work mode): {}", e);
                None
            }
        },
    };

    Config::new(
        matches.opt_str("c").as_str_ref(),
        work_mode,
        matches.opt_str("d").as_str_ref(),
        matches.free.get(0).as_str_ref(),
        matches.free.get(1).as_str_ref(),
        matches.opt_str("o").as_str_ref(),
        matches.opt_str("doc-target-path").as_str_ref(),
        matches.opt_present("b"),
        matches.opt_present("s"),
        matches.opt_present("disable-format"),
    )
    .map(RunKind::Config)
}

#[cfg_attr(test, allow(dead_code))]
fn main() {
    if let Err(ref e) = do_main() {
        eprintln!("{}", e);

        ::std::process::exit(1);
    }
}

fn run_check(check_gir_file: &str) -> Result<(), String> {
    let path = PathBuf::from(check_gir_file);
    if !path.is_file() {
        return Err(format!("`{}`: file not found", check_gir_file));
    }
    let lib_name = path
        .file_stem()
        .ok_or(format!("Failed to get file stem from `{}`", check_gir_file))?;
    let lib_name = lib_name
        .to_str()
        .ok_or("failed to convert OsStr to str".to_owned())?;
    let mut library = Library::new(lib_name);
    let parent = path.parent().ok_or(format!(
        "Failed to get parent directory from `{}`",
        check_gir_file
    ))?;

    library.read_file(&parent, &mut vec![lib_name.to_owned()])
}

fn do_main() -> Result<(), String> {
    if std::env::var_os("RUST_LOG").is_none() {
        std::env::set_var("RUST_LOG", "gir=warn");
        std::env::set_var("RUST_LOG", "libgir=warn");
    }
    env_logger::init();

    let mut cfg = match build_config() {
        Ok(RunKind::CheckGirFile(check_gir_file)) => return run_check(&check_gir_file),
        Ok(RunKind::Config(cfg)) => cfg,
        Err(err) => return Err(err),
    };
    cfg.check_disable_format();

    let statistics = Profiler::new("Gir");
    statistics.start_frame();

    let watcher_total = statistics.enter("Total");

    let mut library = {
        let _watcher = statistics.enter("Loading");

        let mut library = Library::new(&cfg.library_name);
        library.read_file(&cfg.girs_dir, &mut vec![cfg.library_full_name()])?;
        library
    };

    {
        let _watcher = statistics.enter("Preprocessing");
        library.preprocessing(cfg.work_mode);
    }

    {
        let _watcher = statistics.enter("Update library by config");
        gir::update_version::apply_config(&mut library, &cfg);
    }

    {
        let _watcher = statistics.enter("Postprocessing");
        library.postprocessing(&cfg);
    }

    {
        let _watcher = statistics.enter("Resolving type ids");
        cfg.resolve_type_ids(&library);
    }

    {
        let _watcher = statistics.enter("Checking versions");
        gir::update_version::check_function_real_version(&mut library);
    }

    let mut env = {
        let _watcher = statistics.enter("Namespace/symbol/class analysis");

        let namespaces = gir::namespaces_run(&library);
        let symbols = gir::symbols_run(&library, &namespaces);
        let class_hierarchy = gir::class_hierarchy_run(&library);

        gir::Env {
            library,
            config: cfg,
            namespaces,
            symbols: RefCell::new(symbols),
            class_hierarchy,
            analysis: Default::default(),
        }
    };

    if env.config.work_mode != WorkMode::Sys {
        let _watcher = statistics.enter("Analyzing");
        gir::analysis_run(&mut env);
    }

    if env.config.work_mode != WorkMode::DisplayNotBound {
        let _watcher = statistics.enter("Generating");
        gir::codegen_generate(&env);
    }

    if !env.config.disable_format && env.config.work_mode.is_generate_rust_files() {
        let _watcher = statistics.enter("Formatting");
        gir::fmt::format(&env.config.target_path);
    }

    drop(watcher_total);
    statistics.end_frame();

    if env.config.show_statistics {
        statistics.print_timing();
    }
    if env.config.work_mode == WorkMode::DisplayNotBound {
        env.library.show_non_bound_types(&env);
    }

    Ok(())
}
<commit_msg>main: Don't override RUST_LOG=gir=warn with libgir=warn but combine them<commit_after>use std::env;
use std::path::PathBuf;
use std::process;
use std::{cell::RefCell, str::FromStr};

use getopts::Options;
use hprof::Profiler;
use libgir::{self as gir, Config, Library, WorkMode};

fn print_usage(program: &str, opts: Options) {
    let brief = format!(
        "Usage: {program} [options] [<library> <version>]
       {program} (-h | --help)",
        program = program
    );
    print!("{}", opts.usage(&brief));
}

trait OptionStr {
    fn as_str_ref(&self) -> Option<&str>;
}

impl<S: AsRef<str>> OptionStr for Option<S> {
    fn as_str_ref(&self) -> Option<&str> {
        self.as_ref().map(|string| string.as_ref())
    }
}

#[allow(clippy::large_enum_variant)]
enum RunKind {
    Config(Config),
    CheckGirFile(String),
}

fn build_config() -> Result<RunKind, String> {
    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();

    let mut options = Options::new();
    options.optopt(
        "c",
        "config",
        "Config file path (default: Gir.toml)",
        "CONFIG",
    );
    options.optflag("h", "help", "Show this message");
    options.optopt("d", "gir-directory", "Directory for girs", "GIRSPATH");
    options.optopt(
        "m",
        "mode",
        "Work mode: doc, normal, sys or not_bound",
        "MODE",
    );
    options.optopt("o", "target", "Target path", "PATH");
    options.optopt("p", "doc-target-path", "Doc target path", "PATH");
    options.optflag("b", "make-backup", "Make backup before generating");
    options.optflag("s", "stats", "Show statistics");
    options.optflag("", "disable-format", "Disable formatting generated code");
    options.optopt(
        "",
        "check-gir-file",
        "Check if the given `.gir` file is valid",
        "PATH",
    );

    let matches = options.parse(&args[1..]).map_err(|e| e.to_string())?;

    if let Some(check_gir_file) = matches.opt_str("check-gir-file") {
        return Ok(RunKind::CheckGirFile(check_gir_file));
    }

    if matches.opt_present("h") {
        print_usage(&program, options);
        process::exit(0);
    }

    let work_mode = match matches.opt_str("m") {
        None => None,
        Some(s) => match WorkMode::from_str(&s) {
            Ok(w) => Some(w),
            Err(e) => {
                eprintln!("Error (switching to default work mode): {}", e);
                None
            }
        },
    };

    Config::new(
        matches.opt_str("c").as_str_ref(),
        work_mode,
        matches.opt_str("d").as_str_ref(),
        matches.free.get(0).as_str_ref(),
        matches.free.get(1).as_str_ref(),
        matches.opt_str("o").as_str_ref(),
        matches.opt_str("doc-target-path").as_str_ref(),
        matches.opt_present("b"),
        matches.opt_present("s"),
        matches.opt_present("disable-format"),
    )
    .map(RunKind::Config)
}

#[cfg_attr(test, allow(dead_code))]
fn main() {
    if let Err(ref e) = do_main() {
        eprintln!("{}", e);

        ::std::process::exit(1);
    }
}

fn run_check(check_gir_file: &str) -> Result<(), String> {
    let path = PathBuf::from(check_gir_file);
    if !path.is_file() {
        return Err(format!("`{}`: file not found", check_gir_file));
    }
    let lib_name = path
        .file_stem()
        .ok_or(format!("Failed to get file stem from `{}`", check_gir_file))?;
    let lib_name = lib_name
        .to_str()
        .ok_or("failed to convert OsStr to str".to_owned())?;
    let mut library = Library::new(lib_name);
    let parent = path.parent().ok_or(format!(
        "Failed to get parent directory from `{}`",
        check_gir_file
    ))?;

    library.read_file(&parent, &mut vec![lib_name.to_owned()])
}

fn do_main() -> Result<(), String> {
    if std::env::var_os("RUST_LOG").is_none() {
        std::env::set_var("RUST_LOG", "gir=warn,libgir=warn");
    }
    env_logger::init();

    let mut cfg = match build_config() {
        Ok(RunKind::CheckGirFile(check_gir_file)) => return run_check(&check_gir_file),
        Ok(RunKind::Config(cfg)) => cfg,
        Err(err) => return Err(err),
    };
    cfg.check_disable_format();

    let statistics = Profiler::new("Gir");
    statistics.start_frame();

    let watcher_total = statistics.enter("Total");

    let mut library = {
        let _watcher = statistics.enter("Loading");

        let mut library = Library::new(&cfg.library_name);
        library.read_file(&cfg.girs_dir, &mut vec![cfg.library_full_name()])?;
        library
    };

    {
        let _watcher = statistics.enter("Preprocessing");
        library.preprocessing(cfg.work_mode);
    }

    {
        let _watcher = statistics.enter("Update library by config");
        gir::update_version::apply_config(&mut library, &cfg);
    }

    {
        let _watcher = statistics.enter("Postprocessing");
        library.postprocessing(&cfg);
    }

    {
        let _watcher = statistics.enter("Resolving type ids");
        cfg.resolve_type_ids(&library);
    }

    {
        let _watcher = statistics.enter("Checking versions");
        gir::update_version::check_function_real_version(&mut library);
    }

    let mut env = {
        let _watcher = statistics.enter("Namespace/symbol/class analysis");

        let namespaces = gir::namespaces_run(&library);
        let symbols = gir::symbols_run(&library, &namespaces);
        let class_hierarchy = gir::class_hierarchy_run(&library);

        gir::Env {
            library,
            config: cfg,
            namespaces,
            symbols: RefCell::new(symbols),
            class_hierarchy,
            analysis: Default::default(),
        }
    };

    if env.config.work_mode != WorkMode::Sys {
        let _watcher = statistics.enter("Analyzing");
        gir::analysis_run(&mut env);
    }

    if env.config.work_mode != WorkMode::DisplayNotBound {
        let _watcher = statistics.enter("Generating");
        gir::codegen_generate(&env);
    }

    if !env.config.disable_format && env.config.work_mode.is_generate_rust_files() {
        let _watcher = statistics.enter("Formatting");
        gir::fmt::format(&env.config.target_path);
    }

    drop(watcher_total);
    statistics.end_frame();

    if env.config.show_statistics {
        statistics.print_timing();
    }
    if env.config.work_mode == WorkMode::DisplayNotBound {
        env.library.show_non_bound_types(&env);
    }

    Ok(())
}
<|endoftext|>


<commit_before>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Encapsulates has_more_command and avance method from the proposed API.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// Ignore comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					self.current_command = content;
					match self.command_type() {
						ACommand => {
							println!("ACommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
						CCommand => {
							println!("CCommand: {0}", self.current_command);
							println!("\tdest: {0}", self.dest());
							println!("\tcomp: {0}", self.comp());
							println!("\tjump: {0}", self.jump());

						},
						LCommand => {
							println!("LCommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());

						},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. For (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}

	/// Returns the symbol or decimal xxx of the current command @xxx or (xxx).
	///
	/// Should be called only when command_type() is ACommand or LCommand.
	fn symbol(&self) -> String {
		let pattern: &[_] = &['(', ')', '@'];
		self.current_command.trim_matches(pattern).to_string()
	}

	/// Returns the dest mnemonic in the current CCommand. dest=comp;jump
	///
	///(8 posibilities.)
	/// Should only be called when cammand_type() is CCommand.
	fn dest(&self) -> String {
		// dest or jump field may be empy
		// if dest is empty, the '=' is omitted.
		// if jump is empty, the ';' is omitted.
		match self.current_command.contains('=') {
			true => {
				let v: Vec<&str> = self.current_command.split('=').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}

	// /// Returns the comp mnemonic in the current CCommand. dest=comp;jump
	// ///
	// /// 28 possibilities.
	// /// Should only be called when cammand_type() is CCommand.
	fn comp(&self) -> String {
		match self.current_command.contains('=') {
			true => {
				match self.current_command.contains(';') {
					true => { // dest=comp;jump
						let v: Vec<&str> = self.current_command.split(|c: char| c == ';' || c == '=' ).collect();
						v[1].to_string()
					},
					false => { // dest=comp
						let v: Vec<&str> = self.current_command.split('=').collect();
						v[1].to_string()
					}
				}
			},
			false => {
				match self.current_command.contains(';') {
					true => { // comp;jump
						let v: Vec<&str> = self.current_command.split(';').collect();
						v[0].to_string()
					},
					false => { // comp
						self.current_command.clone()
					},
				}
			}
		}
	}

	/// Returns the jump mnemonic in the current CCommand. dest=com;jump
	///
	/// 8-possibilities.
	/// Should only be called when cammand_type() is CCommand.
	fn jump(&self) -> String {
		match self.current_command.contains(';') {
			true => {
				let v: Vec<&str> = self.current_command.split(';').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<commit_msg>parser module is feature complete<commit_after>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Encapsulates has_more_command and avance method from the proposed API.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// ignore line comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					// remove inline commnents
					let mut content_without_inline = String::new();
					if content.contains("//") {
						let v: Vec<&str> = content.split("//").collect();
						content_without_inline = v[0].trim().to_string();
					}

					// decide whether to use content_with_inline or content
					if content_without_inline != String::new() {
						self.current_command = content_without_inline;
					} else {
						self.current_command = content;
					}

					match self.command_type() {
						ACommand => {
							println!("ACommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
						CCommand => {
							println!("CCommand: {0}", self.current_command);
							println!("\tdest: {0}", self.dest());
							println!("\tcomp: {0}", self.comp());
							println!("\tjump: {0}", self.jump());
						},
						LCommand => {
							println!("LCommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. For (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}

	/// Returns the symbol or decimal xxx of the current command @xxx or (xxx).
	///
	/// Should be called only when command_type() is ACommand or LCommand.
	fn symbol(&self) -> String {
		let pattern: &[_] = &['(', ')', '@'];
		self.current_command.trim_matches(pattern).to_string()
	}

	/// Returns the dest mnemonic in the current CCommand. dest=comp;jump
	///
	///(8 posibilities.)
	/// Should only be called when cammand_type() is CCommand.
	fn dest(&self) -> String {
		// dest or jump field may be empy
		// if dest is empty, the '=' is omitted.
		// if jump is empty, the ';' is omitted.
		match self.current_command.contains('=') {
			true => {
				let v: Vec<&str> = self.current_command.split('=').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}

	// /// Returns the comp mnemonic in the current CCommand. dest=comp;jump
	// ///
	// /// 28 possibilities.
	// /// Should only be called when cammand_type() is CCommand.
	fn comp(&self) -> String {
		match self.current_command.contains('=') {
			true => {
				match self.current_command.contains(';') {
					true => { // dest=comp;jump
						let v: Vec<&str> = self.current_command.split(|c: char| c == ';' || c == '=' ).collect();
						v[1].to_string()
					},
					false => { // dest=comp
						let v: Vec<&str> = self.current_command.split('=').collect();
						v[1].to_string()
					}
				}
			},
			false => {
				match self.current_command.contains(';') {
					true => { // comp;jump
						let v: Vec<&str> = self.current_command.split(';').collect();
						v[0].to_string()
					},
					false => { // comp
						self.current_command.clone()
					},
				}
			}
		}
	}

	/// Returns the jump mnemonic in the current CCommand. dest=com;jump
	///
	/// 8-possibilities.
	/// Should only be called when cammand_type() is CCommand.
	fn jump(&self) -> String {
		match self.current_command.contains(';') {
			true => {
				let v: Vec<&str> = self.current_command.split(';').collect();
				return v[1].to_string()
			},
			false => "null".to_string(),
		}
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<|endoftext|>


<commit_before>extern crate open;

use std::io::{stderr, Write};
use std::env;
use std::process;

fn main() {
    let path_or_url =
        match env::args().skip(1).next() {
            Some(arg) => arg,
            None => {
                writeln!(stderr(), "usage: open <path-or-url>");
                process::exit(1);
            }
        };

    if let Err(err) = open::that(&path_or_url) {
        writeln!(stderr(), "An error occourred when opening '{}': {}", path_or_url, err);
        process::exit(3);
    }
}
<commit_msg>fix(open): use result<commit_after>extern crate open;

use std::io::{stderr, Write};
use std::env;
use std::process;

fn main() {
    let path_or_url =
        match env::args().skip(1).next() {
            Some(arg) => arg,
            None => {
                writeln!(stderr(), "usage: open <path-or-url>").ok();
                process::exit(1);
            }
        };

    if let Err(err) = open::that(&path_or_url) {
        writeln!(stderr(), "An error occourred when opening '{}': {}", path_or_url, err).ok();
        process::exit(3);
    }
}
<|endoftext|>


<commit_before>#![allow(non_upper_case_globals)]
//#![allow(dead_code)]

#[macro_use] extern crate lazy_static;

use std::ffi::OsStr;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

lazy_static! {
    static ref HOME: PathBuf = std::env::home_dir().unwrap();

    static ref build_props: PathBuf = PathBuf::from("project/build.properties");

    static ref sbt_launch_dir: PathBuf = { let mut p = PathBuf::from(&*HOME); p.push(".sbt/launchers"); p };
}

const sbt_launch_ivy_release_repo: &'static str = "http://repo.typesafe.com/typesafe/ivy-releases";
const sbt_launch_mvn_release_repo: &'static str = "http://repo.scala-sbt.org/scalasbt/maven-releases";

macro_rules! echoerr(($($arg:tt)*) => (writeln!(&mut ::std::io::stderr(), $($arg)*).unwrap();));

fn build_props_sbt() -> String {
    if let Ok(f) = File::open(&*build_props) {
        let f = BufReader::new(f);
        for line in f.lines() {
            let line = line.unwrap();
            if line.starts_with("sbt.version") {
                let line = line.replace("=", " ");
                let line = line.replace("\r", " ");
                return line.split_whitespace().nth(1).unwrap().to_owned();
            }
        }
    }
    "".to_owned()
}

fn jar_file(version: &str) -> PathBuf {
    let mut p = PathBuf::from(&*sbt_launch_dir);
    p.push(version);
    p.push("sbt-launch.jar");
    p
}

fn url_base(version: &str) -> &'static str { match version {
    s if s.starts_with("0.7.")  => "http://simple-build-tool.googlecode.com",
    s if s.starts_with("0.10.") => sbt_launch_ivy_release_repo,
    "0.11.1" | "0.11.2"         => sbt_launch_ivy_release_repo,
 // "0.*-yyyymmdd-hhMMss"       => sbt_launch_ivy_snapshot_repo, // https://repo.scala-sbt.org/scalasbt/ivy-snapshots
    s if s.starts_with("0.")    => sbt_launch_ivy_release_repo,
 // "*-yyyymmdd-hhMMss"         => sbt_launch_mvn_snapshot_repo, // http://repo.scala-sbt.org/scalasbt/maven-snapshots
    _                           => sbt_launch_mvn_release_repo,
} }

fn make_url(version: &str) -> String {
    let base = url_base(version);
    match version {
        s if s.starts_with("0.7.")  => format!("{}/files/sbt-launch-0.7.7.jar", base),
        s if s.starts_with("0.10.") => format!("{}/org.scala-tools.sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        "0.11.1" | "0.11.2"         => format!("{}/org.scala-tools.sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        s if s.starts_with("0.")    => format!("{}/org.scala-sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        _                           => format!("{}/org/scala-sbt/sbt-launch/{}/sbt-launch.jar", base, version),
    }
}

fn download_url(sbt_version: &str, url: &str, jar: &Path) {
    echoerr!("Downloading sbt launcher for {}:", sbt_version);
    echoerr!("  From  {}", url);
    echoerr!("    To  {}", jar.display());

    std::fs::create_dir_all(jar.parent().unwrap()).unwrap();

    extern crate hyper;
    let mut r = BufReader::new(hyper::client::Client::new().get(url).send().unwrap());
    let mut buf = [0; 16384];
    let mut jar = std::io::BufWriter::new(File::create(jar).unwrap());
    while {
        let bc = r.read(&mut buf).unwrap();
        jar.write(&buf[0..bc]).unwrap();
        bc > 0
    } {}
    jar.flush().unwrap();
}

fn exec_runner<S: AsRef<OsStr>>(args: &[S]) {
    use std::os::unix::process::CommandExt;
    let err = std::process::Command::new(&args[0]).args(&args[1..]).exec();
    println!("error: {}", err);
    if let Some(err) = err.raw_os_error() {
        std::process::exit(err);
    }
    std::process::exit(-1)
}

fn main() {
    let sbt_version = build_props_sbt();
    let sbt_jar = {
        let mut sbt_jar = jar_file(&sbt_version);
        if !sbt_jar.exists() {
            sbt_jar = PathBuf::from(&*HOME);
            sbt_jar.push(format!(".ivy2/local/org.scala-sbt/sbt-launch/{}/jars/sbt-launch.jar", sbt_version));
        }
        if !sbt_jar.exists() {
            sbt_jar = jar_file(&sbt_version);
            download_url(&sbt_version, &make_url(&sbt_version), &sbt_jar);
        }
        sbt_jar
    };

    let java_cmd = "java";
    let extra_jvm_opts = ["-Xms512m", "-Xmx1536m", "-Xss2m"];
    let java_args: [&str; 0] = [];
    let sbt_commands: [&str; 0] = [];
    let residual_args = ["shell"];

    let exec_args: Vec<&OsStr> = {
        let mut exec_args = Vec::new();
        exec_args.push(java_cmd.as_ref());
        exec_args.extend_from_slice(&extra_jvm_opts.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&java_args.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&["-jar".as_ref(), sbt_jar.as_ref()]);
        exec_args.extend_from_slice(&sbt_commands.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&residual_args.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args
    };

    exec_runner(&exec_args)
}
<commit_msg>Some use breathing space<commit_after>#![allow(non_upper_case_globals)]
//#![allow(dead_code)]

#[macro_use] extern crate lazy_static;

use std::ffi::OsStr;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::{ Path, PathBuf };

lazy_static! {
    static ref HOME: PathBuf = std::env::home_dir().unwrap();

    static ref build_props: PathBuf = PathBuf::from("project/build.properties");

    static ref sbt_launch_dir: PathBuf = { let mut p = PathBuf::from(&*HOME); p.push(".sbt/launchers"); p };
}

const sbt_launch_ivy_release_repo: &'static str = "http://repo.typesafe.com/typesafe/ivy-releases";
const sbt_launch_mvn_release_repo: &'static str = "http://repo.scala-sbt.org/scalasbt/maven-releases";

macro_rules! echoerr(($($arg:tt)*) => (writeln!(&mut ::std::io::stderr(), $($arg)*).unwrap();));

fn build_props_sbt() -> String {
    if let Ok(f) = File::open(&*build_props) {
        let f = BufReader::new(f);
        for line in f.lines() {
            let line = line.unwrap();
            if line.starts_with("sbt.version") {
                let line = line.replace("=", " ");
                let line = line.replace("\r", " ");
                return line.split_whitespace().nth(1).unwrap().to_owned();
            }
        }
    }
    "".to_owned()
}

fn jar_file(version: &str) -> PathBuf {
    let mut p = PathBuf::from(&*sbt_launch_dir);
    p.push(version);
    p.push("sbt-launch.jar");
    p
}

fn url_base(version: &str) -> &'static str { match version {
    s if s.starts_with("0.7.")  => "http://simple-build-tool.googlecode.com",
    s if s.starts_with("0.10.") => sbt_launch_ivy_release_repo,
    "0.11.1" | "0.11.2"         => sbt_launch_ivy_release_repo,
 // "0.*-yyyymmdd-hhMMss"       => sbt_launch_ivy_snapshot_repo, // https://repo.scala-sbt.org/scalasbt/ivy-snapshots
    s if s.starts_with("0.")    => sbt_launch_ivy_release_repo,
 // "*-yyyymmdd-hhMMss"         => sbt_launch_mvn_snapshot_repo, // http://repo.scala-sbt.org/scalasbt/maven-snapshots
    _                           => sbt_launch_mvn_release_repo,
} }

fn make_url(version: &str) -> String {
    let base = url_base(version);
    match version {
        s if s.starts_with("0.7.")  => format!("{}/files/sbt-launch-0.7.7.jar", base),
        s if s.starts_with("0.10.") => format!("{}/org.scala-tools.sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        "0.11.1" | "0.11.2"         => format!("{}/org.scala-tools.sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        s if s.starts_with("0.")    => format!("{}/org.scala-sbt/sbt-launch/{}/sbt-launch.jar", base, version),
        _                           => format!("{}/org/scala-sbt/sbt-launch/{}/sbt-launch.jar", base, version),
    }
}

fn download_url(sbt_version: &str, url: &str, jar: &Path) {
    echoerr!("Downloading sbt launcher for {}:", sbt_version);
    echoerr!("  From  {}", url);
    echoerr!("    To  {}", jar.display());

    std::fs::create_dir_all(jar.parent().unwrap()).unwrap();

    extern crate hyper;
    let mut r = BufReader::new(hyper::client::Client::new().get(url).send().unwrap());
    let mut buf = [0; 16384];
    let mut jar = std::io::BufWriter::new(File::create(jar).unwrap());
    while {
        let bc = r.read(&mut buf).unwrap();
        jar.write(&buf[0..bc]).unwrap();
        bc > 0
    } {}
    jar.flush().unwrap();
}

fn exec_runner<S: AsRef<OsStr>>(args: &[S]) {
    use std::os::unix::process::CommandExt;
    let err = std::process::Command::new(&args[0]).args(&args[1..]).exec();
    println!("error: {}", err);
    if let Some(err) = err.raw_os_error() {
        std::process::exit(err);
    }
    std::process::exit(-1)
}

fn main() {
    let sbt_version = build_props_sbt();
    let sbt_jar = {
        let mut sbt_jar = jar_file(&sbt_version);
        if !sbt_jar.exists() {
            sbt_jar = PathBuf::from(&*HOME);
            sbt_jar.push(format!(".ivy2/local/org.scala-sbt/sbt-launch/{}/jars/sbt-launch.jar", sbt_version));
        }
        if !sbt_jar.exists() {
            sbt_jar = jar_file(&sbt_version);
            download_url(&sbt_version, &make_url(&sbt_version), &sbt_jar);
        }
        sbt_jar
    };

    let java_cmd = "java";
    let extra_jvm_opts = ["-Xms512m", "-Xmx1536m", "-Xss2m"];
    let java_args: [&str; 0] = [];
    let sbt_commands: [&str; 0] = [];
    let residual_args = ["shell"];

    let exec_args: Vec<&OsStr> = {
        let mut exec_args = Vec::new();
        exec_args.push(java_cmd.as_ref());
        exec_args.extend_from_slice(&extra_jvm_opts.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&java_args.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&["-jar".as_ref(), sbt_jar.as_ref()]);
        exec_args.extend_from_slice(&sbt_commands.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args.extend_from_slice(&residual_args.iter().map(|x| x.as_ref()).collect::<Vec<_>>());
        exec_args
    };

    exec_runner(&exec_args)
}
<|endoftext|>