view rust/hg-cpython/src/dirstate/dirs_multiset.rs @ 42746:b3518b0baa47

rust-dirstate: create dirstate submodule in hg-cpython This module will soon hold multiple files, this change is to make the review process easier. Differential Revision: https://phab.mercurial-scm.org/D6626
author Raphaël Gomès <rgomes@octobus.net>
date Mon, 08 Jul 2019 18:01:39 +0200
parents rust/hg-cpython/src/dirstate.rs@326fdce22fb2
children 7cae6bc29ff9
line wrap: on
line source

// dirs_multiset.rs
//
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.

//! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
//! `hg-core` package.

use std::cell::RefCell;

use cpython::{
    exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
    ToPyObject,
};

use crate::dirstate::extract_dirstate_vec;
use hg::{DirsIterable, DirsMultiset, DirstateMapError};

py_class!(pub class Dirs |py| {
    data dirs_map: RefCell<DirsMultiset>;

    // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
    // a `list`)
    def __new__(
        _cls,
        map: PyObject,
        skip: Option<PyObject> = None
    ) -> PyResult<Self> {
        let mut skip_state: Option<i8> = None;
        if let Some(skip) = skip {
            skip_state = Some(skip.extract::<PyBytes>(py)?.data(py)[0] as i8);
        }
        let dirs_map;

        if let Ok(map) = map.cast_as::<PyDict>(py) {
            let dirstate_vec = extract_dirstate_vec(py, &map)?;
            dirs_map = DirsMultiset::new(
                DirsIterable::Dirstate(dirstate_vec),
                skip_state,
            )
        } else {
            let map: Result<Vec<Vec<u8>>, PyErr> = map
                .iter(py)?
                .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
                .collect();
            dirs_map = DirsMultiset::new(
                DirsIterable::Manifest(map?),
                skip_state,
            )
        }

        Self::create_instance(py, RefCell::new(dirs_map))
    }

    def addpath(&self, path: PyObject) -> PyResult<PyObject> {
        self.dirs_map(py).borrow_mut().add_path(
            path.extract::<PyBytes>(py)?.data(py),
        );
        Ok(py.None())
    }

    def delpath(&self, path: PyObject) -> PyResult<PyObject> {
        self.dirs_map(py).borrow_mut().delete_path(
            path.extract::<PyBytes>(py)?.data(py),
        )
            .and(Ok(py.None()))
            .or_else(|e| {
                match e {
                    DirstateMapError::PathNotFound(_p) => {
                        Err(PyErr::new::<exc::ValueError, _>(
                            py,
                            "expected a value, found none".to_string(),
                        ))
                    }
                    DirstateMapError::EmptyPath => {
                        Ok(py.None())
                    }
                }
            })
    }

    // This is really inefficient on top of being ugly, but it's an easy way
    // of having it work to continue working on the rest of the module
    // hopefully bypassing Python entirely pretty soon.
    def __iter__(&self) -> PyResult<PyObject> {
        let dict = PyDict::new(py);

        for (key, value) in self.dirs_map(py).borrow().iter() {
            dict.set_item(
                py,
                PyBytes::new(py, &key[..]),
                value.to_py_object(py),
            )?;
        }

        let locals = PyDict::new(py);
        locals.set_item(py, "obj", dict)?;

        py.eval("iter(obj)", None, Some(&locals))
    }

    def __contains__(&self, item: PyObject) -> PyResult<bool> {
        Ok(self
            .dirs_map(py)
            .borrow()
            .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
    }
});