<commit_before>use address::{LENGTH, Address, Addressable};
use node::Node;
use num::bigint::{BigUint, ToBigUint};
use std::cmp;
use std::collections::HashMap;
use std::fmt;
use time;
use num;

const MINUTES_UNTIL_NEEDS_REFRESH: i64 = 15;

#[derive(Debug, PartialEq)]
pub enum InsertOutcome {
    Inserted,   // Inserted new node
    Updated,    // Updated existing node
    Discarded   // Bucket is full
}

pub type InsertionResult = Result<InsertOutcome, String>;

pub struct NodeBucket {
    k: usize,
    min: BigUint,
    max: BigUint,
    addresses: Vec<Address>,
    nodes: HashMap<Address, Box<Node>>,
    last_inserted: time::Tm
}

impl fmt::Debug for NodeBucket {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "NodeBucket {{ [{:040x} - {:040x}], contains: {:?} last_changed: {:?}}}",
               self.min, self.max, self.addresses, self.last_changed())
    }
}

impl NodeBucket {
    pub fn new(k: usize) -> NodeBucket {
        let min = 0.to_biguint().unwrap();
        let max = num::pow(2.to_biguint().unwrap(), LENGTH);
        NodeBucket {
            k: k,
            min: min,
            max: max,
            addresses: Vec::with_capacity(k),
            nodes: HashMap::with_capacity(k),
            last_inserted: time::empty_tm()
        }
    }

    pub fn contains(&self, address: &Address) -> bool {
        self.addresses.contains(address)
    }

    pub fn covers(&self, address: &Address) -> bool {
        let numeric = address.as_numeric();
        self.min <= numeric && numeric < self.max
    }

    pub fn find_node(&mut self, address: &Address) -> Option<&mut Box<Node>> {
        self.nodes.get_mut(address)
    }

    pub fn get_nodes(&mut self) -> Vec<&mut Box<Node>> {
        self.nodes.iter_mut().map(|(_, node)| node).collect()
    }

    pub fn questionable_nodes(&mut self) -> Vec<&mut Box<Node>> {
        self.nodes.iter_mut()
            .map(|(_, node)| node)
            .filter(|n| n.is_questionable())
            .collect()
    }

    pub fn insert(&mut self, node: Box<Node>) -> InsertionResult {
        let address = node.get_address();
        if self.covers(&address) {
            if let Some(pos) = self.addresses.iter().position(|a| a == &address) {
                // TODO: Should this update the node's socket address incase a device has changed
                // IPs?
                self.addresses.remove(pos);
                self.addresses.insert(0, address);
                self.last_inserted = time::now_utc();
                Ok(InsertOutcome::Updated)
            } else if !self.is_full() {
                self.addresses.insert(0, address);
                self.nodes.insert(address, node);
                self.last_inserted = time::now_utc();
                Ok(InsertOutcome::Inserted)
            } else {
                Ok(InsertOutcome::Discarded)
            }
        } else {
            Err(format!("Bucket {:?}  does not cover {:?}", self, address))
        }
    }

    pub fn is_full(&self) -> bool {
        self.addresses.len() >= self.k
    }

    pub fn last_changed(&self) -> time::Tm {
        if let Some((_, node)) = self.nodes.iter().max_by_key(|&(_, node)| node.last_seen()) {
            cmp::max(self.last_inserted, node.last_seen())
        } else {
            self.last_inserted
        }
    }

    pub fn needs_refresh(&self) -> bool {
        let time_since_changed = time::now_utc() - self.last_changed();
        time_since_changed > time::Duration::minutes(MINUTES_UNTIL_NEEDS_REFRESH)
    }

    pub fn random_address_in_space(&self) -> Address {
        Address::random(&self.min, &self.max)
    }

    pub fn split(&mut self) -> (Self, Self) {
        let difference = self.max.clone() - self.min.clone();
        let partition = difference / 2.to_biguint().unwrap() + self.min.clone();
        let addresses = self.addresses.clone();
        let (a_addresses, b_addresses): (Vec<Address>, Vec<Address>) = addresses
            .into_iter()
            .partition(|&a| a.as_numeric() < partition);
        let mut a_nodes = HashMap::new();
        let mut b_nodes = HashMap::new();
        for (address, node) in self.nodes.drain() {
            if address.as_numeric() < partition {
                a_nodes.insert(address, node);
            } else {
                b_nodes.insert(address, node);
            }
        }

        let a = NodeBucket {
            k: self.k,
            min: self.min.clone(),
            max: partition.clone(),
            addresses: a_addresses,
            nodes: a_nodes,
            last_inserted: self.last_inserted
        };
        let b = NodeBucket {
            k: self.k,
            min: partition.clone(),
            max: self.max.clone(),
            addresses: b_addresses,
            nodes: b_nodes,
            last_inserted: self.last_inserted
        };
        (a, b)
    }
}

#[cfg(test)]
mod tests {
    use address::{Address, Addressable};
    use super::{InsertOutcome, NodeBucket};
    use tests::TestNode;
    use time;

    #[test]
    fn test_insert() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        let node = Box::new(TestNode::new(Address::for_content("some string")));
        assert_eq!(bucket.insert(node).unwrap(), InsertOutcome::Inserted);
        assert_eq!(bucket.addresses.len(), 1);
    }

    #[test]
    fn test_insert_duplicate() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        let addr_1 = Address::for_content("node 1");
        let addr_2 = Address::for_content("node 2");
        let addr_3 = Address::for_content("node 1");
        let a = Box::new(TestNode::new(addr_1));
        let b = Box::new(TestNode::new(addr_2));
        let c = Box::new(TestNode::new(addr_3));

        bucket.insert(a).unwrap();
        bucket.insert(b).unwrap();
        assert_eq!(bucket.insert(c).unwrap(), InsertOutcome::Updated);

        assert_eq!(bucket.addresses.len(), 2);
        assert_eq!(bucket.nodes[&Address::for_content("node 1")].get_address(), addr_1);
    }

    #[test]
    fn test_insert_full() {
        let mut bucket: NodeBucket = NodeBucket::new(2);
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 2")))).unwrap();
        let result = bucket.insert(Box::new(TestNode::new(Address::for_content("node 3")))).unwrap();
        assert_eq!(result, InsertOutcome::Discarded);
        assert_eq!(bucket.addresses.len(), 2);
        assert_eq!(bucket.nodes.get(&Address::for_content("node 3")), None);
    }

    #[test]
    fn insert_node_outside_of_address_space() {
        use num::bigint::ToBigUint;
        use std::collections::HashMap;

        let mut bucket = NodeBucket {
            k: 8,
            min: 0.to_biguint().unwrap(),
            max: 1.to_biguint().unwrap(),
            addresses: vec![],
            nodes: HashMap::new(),
            last_inserted: time::empty_tm()
        };
        let result = bucket.insert(Box::new(TestNode::new(Address::for_content("node 3"))));
        assert!(result.is_err());
    }

    #[test]
    fn test_is_full() {
        let mut bucket: NodeBucket = NodeBucket::new(2);
        assert!(!bucket.is_full());

        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 2")))).unwrap();
        assert!(bucket.is_full());
    }

    #[test]
    fn test_contains() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        assert!(bucket.contains(&Address::for_content("node 1")));
    }

    #[test]
    fn test_covers() {
        let bucket: NodeBucket = NodeBucket::new(8);
        let address_1 = Address::from_str("0000000000000000000000000000000000000000");
        let address_2 = Address::from_str("ffffffffffffffffffffffffffffffffffffffff");
        assert!(bucket.covers(&address_1));
        assert!(bucket.covers(&address_2));
    }

    #[test]
    fn test_split() {
        let mut bucket: NodeBucket = NodeBucket::new(4);
        let addr_1 = Address::from_str("0000000000000000000000000000000000000000");
        let addr_2 = Address::from_str("7fffffffffffffffffffffffffffffffffffffff");
        let addr_3 = Address::from_str("8000000000000000000000000000000000000000");
        let addr_4 = Address::from_str("ffffffffffffffffffffffffffffffffffffffff");
        let node_1 = Box::new(TestNode::new(addr_1));
        let node_2 = Box::new(TestNode::new(addr_2));
        let node_3 = Box::new(TestNode::new(addr_3));
        let node_4 = Box::new(TestNode::new(addr_4));

        bucket.insert(node_1).unwrap();
        bucket.insert(node_2).unwrap();
        bucket.insert(node_3).unwrap();
        bucket.insert(node_4).unwrap();

        let (a, b) = bucket.split();

        // Splits up known address
        assert_eq!(a.addresses.len(), 2);
        assert_eq!(b.addresses.len(), 2);

        // Splits up known nodes
        assert_eq!(a.nodes[&Address::from_str("0000000000000000000000000000000000000000")].get_address(), addr_1);
        assert_eq!(a.nodes[&Address::from_str("0000000000000000000000000000000000000000")].get_address(), addr_1);
        assert_eq!(a.nodes[&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")].get_address(), addr_2);
        assert_eq!(b.nodes[&Address::from_str("8000000000000000000000000000000000000000")].get_address(), addr_3);
        assert_eq!(b.nodes[&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")].get_address(), addr_4);

        // Equitably covers address space
        assert!(a.covers(&Address::from_str("0000000000000000000000000000000000000000")));
        assert!(!b.covers(&Address::from_str("0000000000000000000000000000000000000000")));
        assert!(a.covers(&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")));
        assert!(!b.covers(&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")));
        assert!(!a.covers(&Address::from_str("8000000000000000000000000000000000000000")));
        assert!(b.covers(&Address::from_str("8000000000000000000000000000000000000000")));
        assert!(!a.covers(&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")));
        assert!(b.covers(&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")));
    }

    #[test]
    fn test_last_changed() {
        let mut bucket: NodeBucket = NodeBucket::new(4);

        // it updates upon inserting a node
        let last_changed_before_insert = bucket.last_changed();
        let addr = Address::from_str("0000000000000000000000000000000000000000");
        let node = Box::new(TestNode::new(addr));
        bucket.insert(node).unwrap();
        let last_changed_after_insert = bucket.last_changed();
        assert!(last_changed_before_insert < last_changed_after_insert);

        // it updates when one of its nodes are heard from
        let last_changed_before_received = bucket.last_changed();
        bucket.find_node(&addr).unwrap().received_response(1);
        let last_changed_after_received = bucket.last_changed();
        assert!(last_changed_before_received < last_changed_after_received);
    }
}
<commit_msg>Reduce unecessary cloning by moving self into split<commit_after>use address::{LENGTH, Address, Addressable};
use node::Node;
use num::bigint::{BigUint, ToBigUint};
use std::cmp;
use std::collections::HashMap;
use std::fmt;
use time;
use num;

const MINUTES_UNTIL_NEEDS_REFRESH: i64 = 15;

#[derive(Debug, PartialEq)]
pub enum InsertOutcome {
    Inserted,   // Inserted new node
    Updated,    // Updated existing node
    Discarded   // Bucket is full
}

pub type InsertionResult = Result<InsertOutcome, String>;

pub struct NodeBucket {
    k: usize,
    min: BigUint,
    max: BigUint,
    addresses: Vec<Address>,
    nodes: HashMap<Address, Box<Node>>,
    last_inserted: time::Tm
}

impl fmt::Debug for NodeBucket {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "NodeBucket {{ [{:040x} - {:040x}], contains: {:?} last_changed: {:?}}}",
               self.min, self.max, self.addresses, self.last_changed())
    }
}

impl NodeBucket {
    pub fn new(k: usize) -> NodeBucket {
        let min = 0.to_biguint().unwrap();
        let max = num::pow(2.to_biguint().unwrap(), LENGTH);
        NodeBucket {
            k: k,
            min: min,
            max: max,
            addresses: Vec::with_capacity(k),
            nodes: HashMap::with_capacity(k),
            last_inserted: time::empty_tm()
        }
    }

    pub fn contains(&self, address: &Address) -> bool {
        self.addresses.contains(address)
    }

    pub fn covers(&self, address: &Address) -> bool {
        let numeric = address.as_numeric();
        self.min <= numeric && numeric < self.max
    }

    pub fn find_node(&mut self, address: &Address) -> Option<&mut Box<Node>> {
        self.nodes.get_mut(address)
    }

    pub fn get_nodes(&mut self) -> Vec<&mut Box<Node>> {
        self.nodes.iter_mut().map(|(_, node)| node).collect()
    }

    pub fn questionable_nodes(&mut self) -> Vec<&mut Box<Node>> {
        self.nodes.iter_mut()
            .map(|(_, node)| node)
            .filter(|n| n.is_questionable())
            .collect()
    }

    pub fn insert(&mut self, node: Box<Node>) -> InsertionResult {
        let address = node.get_address();
        if self.covers(&address) {
            if let Some(pos) = self.addresses.iter().position(|a| a == &address) {
                // TODO: Should this update the node's socket address incase a device has changed
                // IPs?
                self.addresses.remove(pos);
                self.addresses.insert(0, address);
                self.last_inserted = time::now_utc();
                Ok(InsertOutcome::Updated)
            } else if !self.is_full() {
                self.addresses.insert(0, address);
                self.nodes.insert(address, node);
                self.last_inserted = time::now_utc();
                Ok(InsertOutcome::Inserted)
            } else {
                Ok(InsertOutcome::Discarded)
            }
        } else {
            Err(format!("Bucket {:?}  does not cover {:?}", self, address))
        }
    }

    pub fn is_full(&self) -> bool {
        self.addresses.len() >= self.k
    }

    pub fn last_changed(&self) -> time::Tm {
        if let Some((_, node)) = self.nodes.iter().max_by_key(|&(_, node)| node.last_seen()) {
            cmp::max(self.last_inserted, node.last_seen())
        } else {
            self.last_inserted
        }
    }

    pub fn needs_refresh(&self) -> bool {
        let time_since_changed = time::now_utc() - self.last_changed();
        time_since_changed > time::Duration::minutes(MINUTES_UNTIL_NEEDS_REFRESH)
    }

    pub fn random_address_in_space(&self) -> Address {
        Address::random(&self.min, &self.max)
    }

    pub fn split(self) -> (Self, Self) {
        let difference = &self.max - &self.min;
        let partition = difference / 2.to_biguint().unwrap() + &self.min;
        let (a_addresses, b_addresses) = self.addresses
            .into_iter()
            .partition(|&a| a.as_numeric() < partition);
        let mut a_nodes = HashMap::new();
        let mut b_nodes = HashMap::new();
        for (address, node) in self.nodes.into_iter() {
            if address.as_numeric() < partition {
                a_nodes.insert(address, node);
            } else {
                b_nodes.insert(address, node);
            }
        }

        let a = NodeBucket {
            k: self.k,
            min: self.min,
            max: partition.clone(),
            addresses: a_addresses,
            nodes: a_nodes,
            last_inserted: self.last_inserted
        };
        let b = NodeBucket {
            k: self.k,
            min: partition,
            max: self.max,
            addresses: b_addresses,
            nodes: b_nodes,
            last_inserted: self.last_inserted
        };
        (a, b)
    }
}

#[cfg(test)]
mod tests {
    use address::{Address, Addressable};
    use super::{InsertOutcome, NodeBucket};
    use tests::TestNode;
    use time;

    #[test]
    fn test_insert() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        let node = Box::new(TestNode::new(Address::for_content("some string")));
        assert_eq!(bucket.insert(node).unwrap(), InsertOutcome::Inserted);
        assert_eq!(bucket.addresses.len(), 1);
    }

    #[test]
    fn test_insert_duplicate() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        let addr_1 = Address::for_content("node 1");
        let addr_2 = Address::for_content("node 2");
        let addr_3 = Address::for_content("node 1");
        let a = Box::new(TestNode::new(addr_1));
        let b = Box::new(TestNode::new(addr_2));
        let c = Box::new(TestNode::new(addr_3));

        bucket.insert(a).unwrap();
        bucket.insert(b).unwrap();
        assert_eq!(bucket.insert(c).unwrap(), InsertOutcome::Updated);

        assert_eq!(bucket.addresses.len(), 2);
        assert_eq!(bucket.nodes[&Address::for_content("node 1")].get_address(), addr_1);
    }

    #[test]
    fn test_insert_full() {
        let mut bucket: NodeBucket = NodeBucket::new(2);
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 2")))).unwrap();
        let result = bucket.insert(Box::new(TestNode::new(Address::for_content("node 3")))).unwrap();
        assert_eq!(result, InsertOutcome::Discarded);
        assert_eq!(bucket.addresses.len(), 2);
        assert_eq!(bucket.nodes.get(&Address::for_content("node 3")), None);
    }

    #[test]
    fn insert_node_outside_of_address_space() {
        use num::bigint::ToBigUint;
        use std::collections::HashMap;

        let mut bucket = NodeBucket {
            k: 8,
            min: 0.to_biguint().unwrap(),
            max: 1.to_biguint().unwrap(),
            addresses: vec![],
            nodes: HashMap::new(),
            last_inserted: time::empty_tm()
        };
        let result = bucket.insert(Box::new(TestNode::new(Address::for_content("node 3"))));
        assert!(result.is_err());
    }

    #[test]
    fn test_is_full() {
        let mut bucket: NodeBucket = NodeBucket::new(2);
        assert!(!bucket.is_full());

        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 2")))).unwrap();
        assert!(bucket.is_full());
    }

    #[test]
    fn test_contains() {
        let mut bucket: NodeBucket = NodeBucket::new(8);
        bucket.insert(Box::new(TestNode::new(Address::for_content("node 1")))).unwrap();
        assert!(bucket.contains(&Address::for_content("node 1")));
    }

    #[test]
    fn test_covers() {
        let bucket: NodeBucket = NodeBucket::new(8);
        let address_1 = Address::from_str("0000000000000000000000000000000000000000");
        let address_2 = Address::from_str("ffffffffffffffffffffffffffffffffffffffff");
        assert!(bucket.covers(&address_1));
        assert!(bucket.covers(&address_2));
    }

    #[test]
    fn test_split() {
        let mut bucket: NodeBucket = NodeBucket::new(4);
        let addr_1 = Address::from_str("0000000000000000000000000000000000000000");
        let addr_2 = Address::from_str("7fffffffffffffffffffffffffffffffffffffff");
        let addr_3 = Address::from_str("8000000000000000000000000000000000000000");
        let addr_4 = Address::from_str("ffffffffffffffffffffffffffffffffffffffff");
        let node_1 = Box::new(TestNode::new(addr_1));
        let node_2 = Box::new(TestNode::new(addr_2));
        let node_3 = Box::new(TestNode::new(addr_3));
        let node_4 = Box::new(TestNode::new(addr_4));

        bucket.insert(node_1).unwrap();
        bucket.insert(node_2).unwrap();
        bucket.insert(node_3).unwrap();
        bucket.insert(node_4).unwrap();

        let (a, b) = bucket.split();

        // Splits up known address
        assert_eq!(a.addresses.len(), 2);
        assert_eq!(b.addresses.len(), 2);

        // Splits up known nodes
        assert_eq!(a.nodes[&Address::from_str("0000000000000000000000000000000000000000")].get_address(), addr_1);
        assert_eq!(a.nodes[&Address::from_str("0000000000000000000000000000000000000000")].get_address(), addr_1);
        assert_eq!(a.nodes[&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")].get_address(), addr_2);
        assert_eq!(b.nodes[&Address::from_str("8000000000000000000000000000000000000000")].get_address(), addr_3);
        assert_eq!(b.nodes[&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")].get_address(), addr_4);

        // Equitably covers address space
        assert!(a.covers(&Address::from_str("0000000000000000000000000000000000000000")));
        assert!(!b.covers(&Address::from_str("0000000000000000000000000000000000000000")));
        assert!(a.covers(&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")));
        assert!(!b.covers(&Address::from_str("7fffffffffffffffffffffffffffffffffffffff")));
        assert!(!a.covers(&Address::from_str("8000000000000000000000000000000000000000")));
        assert!(b.covers(&Address::from_str("8000000000000000000000000000000000000000")));
        assert!(!a.covers(&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")));
        assert!(b.covers(&Address::from_str("ffffffffffffffffffffffffffffffffffffffff")));
    }

    #[test]
    fn test_last_changed() {
        let mut bucket: NodeBucket = NodeBucket::new(4);

        // it updates upon inserting a node
        let last_changed_before_insert = bucket.last_changed();
        let addr = Address::from_str("0000000000000000000000000000000000000000");
        let node = Box::new(TestNode::new(addr));
        bucket.insert(node).unwrap();
        let last_changed_after_insert = bucket.last_changed();
        assert!(last_changed_before_insert < last_changed_after_insert);

        // it updates when one of its nodes are heard from
        let last_changed_before_received = bucket.last_changed();
        bucket.find_node(&addr).unwrap().received_response(1);
        let last_changed_after_received = bucket.last_changed();
        assert!(last_changed_before_received < last_changed_after_received);
    }
}
<|endoftext|>


<commit_before>#![warn(clippy::pedantic)]

use crate::cacache::Cache;
use anyhow::{anyhow, Context};
use rayon::prelude::*;
use serde::Deserialize;
use std::{
    collections::HashMap,
    env, fmt, fs,
    path::Path,
    process::{self, Command},
};
use tempfile::tempdir;
use url::Url;

mod cacache;

#[derive(Deserialize)]
struct PackageLock {
    #[serde(rename = "lockfileVersion")]
    version: u8,
    dependencies: Option<HashMap<String, OldPackage>>,
    packages: Option<HashMap<String, Package>>,
}

#[derive(Deserialize)]
struct OldPackage {
    version: UrlOrString,
    resolved: Option<UrlOrString>,
    integrity: Option<String>,
    dependencies: Option<HashMap<String, OldPackage>>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Package {
    resolved: Option<UrlOrString>,
    integrity: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
enum UrlOrString {
    Url(Url),
    String(String),
}

impl fmt::Display for UrlOrString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UrlOrString::Url(url) => url.fmt(f),
            UrlOrString::String(string) => string.fmt(f),
        }
    }
}

#[allow(clippy::case_sensitive_file_extension_comparisons)]
fn to_new_packages(
    old_packages: HashMap<String, OldPackage>,
    initial_url: &Url,
) -> anyhow::Result<HashMap<String, Package>> {
    let mut new = HashMap::new();

    for (name, mut package) in old_packages {
        if let UrlOrString::Url(v) = &package.version {
            for (scheme, host) in [
                ("github", "github.com"),
                ("bitbucket", "bitbucket.org"),
                ("gitlab", "gitlab.com"),
            ] {
                if v.scheme() == scheme {
                    package.version = {
                        let mut new_url = initial_url.clone();

                        new_url.set_host(Some(host))?;

                        if v.path().ends_with(".git") {
                            new_url.set_path(v.path());
                        } else {
                            new_url.set_path(&format!("{}.git", v.path()));
                        }

                        new_url.set_fragment(v.fragment());

                        UrlOrString::Url(new_url)
                    };

                    break;
                }
            }
        }

        new.insert(
            format!("{name}-{}", package.version),
            Package {
                resolved: if matches!(package.version, UrlOrString::Url(_)) {
                    Some(package.version)
                } else {
                    package.resolved
                },
                integrity: package.integrity,
            },
        );

        if let Some(dependencies) = package.dependencies {
            new.extend(to_new_packages(dependencies, initial_url)?);
        }
    }

    Ok(new)
}

#[allow(clippy::case_sensitive_file_extension_comparisons)]
fn get_hosted_git_url(url: &Url) -> Option<Url> {
    if ["git", "http", "git+ssh", "git+https", "ssh", "https"].contains(&url.scheme()) {
        let mut s = url.path_segments()?;

        match url.host_str()? {
            "github.com" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let typ = s.next();
                let mut commit = s.next();

                if typ.is_none() {
                    commit = url.fragment();
                } else if typ.is_some() && typ != Some("tree") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = commit.unwrap();

                Some(
                    Url::parse(&format!(
                        "https://codeload.github.com/{user}/{project}/tar.gz/{commit}"
                    ))
                    .ok()?,
                )
            }
            "bitbucket.org" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let aux = s.next();

                if aux == Some("get") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                        "https://bitbucket.org/{user}/{project}/get/{commit}.tar.gz"
                    ))
                    .ok()?,
                )
            }
            "gitlab.com" => {
                let path = &url.path()[1..];

                if path.contains("/~/") || path.contains("/archive.tar.gz") {
                    return None;
                }

                let user = s.next()?;
                let mut project = s.next()?;

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                    "https://gitlab.com/{user}/{project}/repository/archive.tar.gz?ref={commit}"
                ))
                    .ok()?,
                )
            }
            "git.sr.ht" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let aux = s.next();

                if aux == Some("archive") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                        "https://git.sr.ht/{user}/{project}/archive/{commit}.tar.gz"
                    ))
                    .ok()?,
                )
            }
            _ => None,
        }
    } else {
        None
    }
}

fn get_ideal_hash(integrity: &str) -> anyhow::Result<&str> {
    let split: Vec<_> = integrity.split_ascii_whitespace().collect();

    if split.len() == 1 {
        Ok(split[0])
    } else {
        for hash in ["sha512-", "sha1-"] {
            if let Some(h) = split.iter().find(|s| s.starts_with(hash)) {
                return Ok(h);
            }
        }

        Err(anyhow!("not sure which hash to select out of {split:?}"))
    }
}

fn get_initial_url() -> anyhow::Result<Url> {
    Url::parse("git+ssh://git@a.b").context("initial url should be valid")
}

fn main() -> anyhow::Result<()> {
    let args = env::args().collect::<Vec<_>>();

    if args.len() < 2 {
        println!("usage: {} <path/to/package-lock.json>", args[0]);
        println!();
        println!("Prefetches npm dependencies for usage by fetchNpmDeps.");

        process::exit(1);
    }

    let lock_content = fs::read_to_string(&args[1])?;
    let lock: PackageLock = serde_json::from_str(&lock_content)?;

    let out_tempdir;

    let (out, print_hash) = if let Some(path) = args.get(2) {
        (Path::new(path), false)
    } else {
        out_tempdir = tempdir()?;

        (out_tempdir.path(), true)
    };

    let agent = ureq::agent();

    eprintln!("lockfile version: {}", lock.version);

    let packages = match lock.version {
        1 => {
            let initial_url = get_initial_url()?;

            lock.dependencies
                .map(|p| to_new_packages(p, &initial_url))
                .transpose()?
        }
        2 | 3 => lock.packages,
        _ => panic!(
            "We don't support lockfile version {}, please file an issue.",
            lock.version
        ),
    };

    if packages.is_none() {
        return Ok(());
    }

    let cache = Cache::new(out.join("_cacache"));

    packages
        .unwrap()
        .into_par_iter()
        .filter(|(dep, _)| !dep.is_empty())
        .filter(|(_, package)| matches!(package.resolved, Some(UrlOrString::Url(_))))
        .try_for_each(|(dep, package)| {
            eprintln!("{dep}");

            let mut resolved = match package.resolved {
                Some(UrlOrString::Url(url)) => url,
                _ => unreachable!(),
            };

            if let Some(hosted_git_url) = get_hosted_git_url(&resolved) {
                resolved = hosted_git_url;
            }

            let mut data = Vec::new();

            agent
                .get(resolved.as_str())
                .call()?
                .into_reader()
                .read_to_end(&mut data)?;

            cache
                .put(
                    format!("make-fetch-happen:request-cache:{resolved}"),
                    resolved,
                    &data,
                    package
                        .integrity
                        .map(|i| Ok::<String, anyhow::Error>(get_ideal_hash(&i)?.to_string()))
                        .transpose()?,
                )
                .map_err(|e| anyhow!("couldn't insert cache entry for {dep}: {e:?}"))?;

            Ok::<_, anyhow::Error>(())
        })?;

    fs::write(out.join("package-lock.json"), lock_content)?;

    if print_hash {
        Command::new("nix")
            .args(["--experimental-features", "nix-command", "hash", "path"])
            .arg(out.as_os_str())
            .status()?;
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{
        get_hosted_git_url, get_ideal_hash, get_initial_url, to_new_packages, OldPackage, Package,
        UrlOrString,
    };
    use std::collections::HashMap;
    use url::Url;

    #[test]
    fn hosted_git_urls() {
        for (input, expected) in [
            (
                "git+ssh://git@github.com/castlabs/electron-releases.git#fc5f78d046e8d7cdeb66345a2633c383ab41f525",
                Some("https://codeload.github.com/castlabs/electron-releases/tar.gz/fc5f78d046e8d7cdeb66345a2633c383ab41f525"),
            ),
            (
                "https://user@github.com/foo/bar#fix/bug",
                Some("https://codeload.github.com/foo/bar/tar.gz/fix/bug")
            ),
            (
                "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
                None
            ),
            (
                "git+ssh://bitbucket.org/foo/bar#branch",
                Some("https://bitbucket.org/foo/bar/get/branch.tar.gz")
            ),
            (
                "ssh://git@gitlab.com/foo/bar.git#fix/bug",
                Some("https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=fix/bug")
            ),
            (
                "git+ssh://git.sr.ht/~foo/bar#branch",
                Some("https://git.sr.ht/~foo/bar/archive/branch.tar.gz")
            ),
        ] {
            assert_eq!(
                get_hosted_git_url(&Url::parse(input).unwrap()),
                expected.map(|u| Url::parse(u).unwrap())
            );
        }
    }

    #[test]
    fn ideal_hashes() {
        for (input, expected) in [
            ("sha512-foo sha1-bar", Some("sha512-foo")),
            ("sha1-bar md5-foo", Some("sha1-bar")),
            ("sha1-bar", Some("sha1-bar")),
            ("sha512-foo", Some("sha512-foo")),
            ("foo-bar sha1-bar", Some("sha1-bar")),
            ("foo-bar baz-foo", None),
        ] {
            assert_eq!(get_ideal_hash(input).ok(), expected);
        }
    }

    #[test]
    fn git_shorthand_v1() -> anyhow::Result<()> {
        let old =
            {
                let mut o = HashMap::new();
                o.insert(
                String::from("sqlite3"),
                OldPackage {
                    version: UrlOrString::Url(Url::parse(
                        "github:mapbox/node-sqlite3#593c9d498be2510d286349134537e3bf89401c4a",
                    ).unwrap()),
                    resolved: None,
                    integrity: None,
                    dependencies: None,
                },
            );
                o
            };

        let initial_url = get_initial_url()?;

        let new = to_new_packages(old, &initial_url)?;

        assert_eq!(new.len(), 1, "new packages map should contain 1 value");
        assert_eq!(new.into_values().next().unwrap(), Package {
            resolved: Some(UrlOrString::Url(Url::parse("git+ssh://git@github.com/mapbox/node-sqlite3.git#593c9d498be2510d286349134537e3bf89401c4a").unwrap())),
            integrity: None
        });

        Ok(())
    }
}
<commit_msg>prefetch-npm-deps: skip bundled dependencies in v1 lockfiles<commit_after>#![warn(clippy::pedantic)]

use crate::cacache::Cache;
use anyhow::{anyhow, Context};
use rayon::prelude::*;
use serde::Deserialize;
use std::{
    collections::HashMap,
    env, fmt, fs,
    path::Path,
    process::{self, Command},
};
use tempfile::tempdir;
use url::Url;

mod cacache;

#[derive(Deserialize)]
struct PackageLock {
    #[serde(rename = "lockfileVersion")]
    version: u8,
    dependencies: Option<HashMap<String, OldPackage>>,
    packages: Option<HashMap<String, Package>>,
}

#[derive(Deserialize)]
struct OldPackage {
    version: UrlOrString,
    #[serde(default)]
    bundled: bool,
    resolved: Option<UrlOrString>,
    integrity: Option<String>,
    dependencies: Option<HashMap<String, OldPackage>>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Package {
    resolved: Option<UrlOrString>,
    integrity: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
enum UrlOrString {
    Url(Url),
    String(String),
}

impl fmt::Display for UrlOrString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UrlOrString::Url(url) => url.fmt(f),
            UrlOrString::String(string) => string.fmt(f),
        }
    }
}

#[allow(clippy::case_sensitive_file_extension_comparisons)]
fn to_new_packages(
    old_packages: HashMap<String, OldPackage>,
    initial_url: &Url,
) -> anyhow::Result<HashMap<String, Package>> {
    let mut new = HashMap::new();

    for (name, mut package) in old_packages {
        // In some cases, a bundled dependency happens to have the same version as a non-bundled one, causing
        // the bundled one without a URL to override the entry for the non-bundled instance, which prevents the
        // dependency from being downloaded.
        if package.bundled {
            continue;
        }

        if let UrlOrString::Url(v) = &package.version {
            for (scheme, host) in [
                ("github", "github.com"),
                ("bitbucket", "bitbucket.org"),
                ("gitlab", "gitlab.com"),
            ] {
                if v.scheme() == scheme {
                    package.version = {
                        let mut new_url = initial_url.clone();

                        new_url.set_host(Some(host))?;

                        if v.path().ends_with(".git") {
                            new_url.set_path(v.path());
                        } else {
                            new_url.set_path(&format!("{}.git", v.path()));
                        }

                        new_url.set_fragment(v.fragment());

                        UrlOrString::Url(new_url)
                    };

                    break;
                }
            }
        }

        new.insert(
            format!("{name}-{}", package.version),
            Package {
                resolved: if matches!(package.version, UrlOrString::Url(_)) {
                    Some(package.version)
                } else {
                    package.resolved
                },
                integrity: package.integrity,
            },
        );

        if let Some(dependencies) = package.dependencies {
            new.extend(to_new_packages(dependencies, initial_url)?);
        }
    }

    Ok(new)
}

#[allow(clippy::case_sensitive_file_extension_comparisons)]
fn get_hosted_git_url(url: &Url) -> Option<Url> {
    if ["git", "http", "git+ssh", "git+https", "ssh", "https"].contains(&url.scheme()) {
        let mut s = url.path_segments()?;

        match url.host_str()? {
            "github.com" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let typ = s.next();
                let mut commit = s.next();

                if typ.is_none() {
                    commit = url.fragment();
                } else if typ.is_some() && typ != Some("tree") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = commit.unwrap();

                Some(
                    Url::parse(&format!(
                        "https://codeload.github.com/{user}/{project}/tar.gz/{commit}"
                    ))
                    .ok()?,
                )
            }
            "bitbucket.org" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let aux = s.next();

                if aux == Some("get") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                        "https://bitbucket.org/{user}/{project}/get/{commit}.tar.gz"
                    ))
                    .ok()?,
                )
            }
            "gitlab.com" => {
                let path = &url.path()[1..];

                if path.contains("/~/") || path.contains("/archive.tar.gz") {
                    return None;
                }

                let user = s.next()?;
                let mut project = s.next()?;

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                    "https://gitlab.com/{user}/{project}/repository/archive.tar.gz?ref={commit}"
                ))
                    .ok()?,
                )
            }
            "git.sr.ht" => {
                let user = s.next()?;
                let mut project = s.next()?;
                let aux = s.next();

                if aux == Some("archive") {
                    return None;
                }

                if project.ends_with(".git") {
                    project = project.strip_suffix(".git")?;
                }

                let commit = url.fragment()?;

                Some(
                    Url::parse(&format!(
                        "https://git.sr.ht/{user}/{project}/archive/{commit}.tar.gz"
                    ))
                    .ok()?,
                )
            }
            _ => None,
        }
    } else {
        None
    }
}

fn get_ideal_hash(integrity: &str) -> anyhow::Result<&str> {
    let split: Vec<_> = integrity.split_ascii_whitespace().collect();

    if split.len() == 1 {
        Ok(split[0])
    } else {
        for hash in ["sha512-", "sha1-"] {
            if let Some(h) = split.iter().find(|s| s.starts_with(hash)) {
                return Ok(h);
            }
        }

        Err(anyhow!("not sure which hash to select out of {split:?}"))
    }
}

fn get_initial_url() -> anyhow::Result<Url> {
    Url::parse("git+ssh://git@a.b").context("initial url should be valid")
}

fn main() -> anyhow::Result<()> {
    let args = env::args().collect::<Vec<_>>();

    if args.len() < 2 {
        println!("usage: {} <path/to/package-lock.json>", args[0]);
        println!();
        println!("Prefetches npm dependencies for usage by fetchNpmDeps.");

        process::exit(1);
    }

    let lock_content = fs::read_to_string(&args[1])?;
    let lock: PackageLock = serde_json::from_str(&lock_content)?;

    let out_tempdir;

    let (out, print_hash) = if let Some(path) = args.get(2) {
        (Path::new(path), false)
    } else {
        out_tempdir = tempdir()?;

        (out_tempdir.path(), true)
    };

    let agent = ureq::agent();

    eprintln!("lockfile version: {}", lock.version);

    let packages = match lock.version {
        1 => {
            let initial_url = get_initial_url()?;

            lock.dependencies
                .map(|p| to_new_packages(p, &initial_url))
                .transpose()?
        }
        2 | 3 => lock.packages,
        _ => panic!(
            "We don't support lockfile version {}, please file an issue.",
            lock.version
        ),
    };

    if packages.is_none() {
        return Ok(());
    }

    let cache = Cache::new(out.join("_cacache"));

    packages
        .unwrap()
        .into_par_iter()
        .filter(|(dep, _)| !dep.is_empty())
        .filter(|(_, package)| matches!(package.resolved, Some(UrlOrString::Url(_))))
        .try_for_each(|(dep, package)| {
            eprintln!("{dep}");

            let mut resolved = match package.resolved {
                Some(UrlOrString::Url(url)) => url,
                _ => unreachable!(),
            };

            if let Some(hosted_git_url) = get_hosted_git_url(&resolved) {
                resolved = hosted_git_url;
            }

            let mut data = Vec::new();

            agent
                .get(resolved.as_str())
                .call()?
                .into_reader()
                .read_to_end(&mut data)?;

            cache
                .put(
                    format!("make-fetch-happen:request-cache:{resolved}"),
                    resolved,
                    &data,
                    package
                        .integrity
                        .map(|i| Ok::<String, anyhow::Error>(get_ideal_hash(&i)?.to_string()))
                        .transpose()?,
                )
                .map_err(|e| anyhow!("couldn't insert cache entry for {dep}: {e:?}"))?;

            Ok::<_, anyhow::Error>(())
        })?;

    fs::write(out.join("package-lock.json"), lock_content)?;

    if print_hash {
        Command::new("nix")
            .args(["--experimental-features", "nix-command", "hash", "path"])
            .arg(out.as_os_str())
            .status()?;
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{
        get_hosted_git_url, get_ideal_hash, get_initial_url, to_new_packages, OldPackage, Package,
        UrlOrString,
    };
    use std::collections::HashMap;
    use url::Url;

    #[test]
    fn hosted_git_urls() {
        for (input, expected) in [
            (
                "git+ssh://git@github.com/castlabs/electron-releases.git#fc5f78d046e8d7cdeb66345a2633c383ab41f525",
                Some("https://codeload.github.com/castlabs/electron-releases/tar.gz/fc5f78d046e8d7cdeb66345a2633c383ab41f525"),
            ),
            (
                "https://user@github.com/foo/bar#fix/bug",
                Some("https://codeload.github.com/foo/bar/tar.gz/fix/bug")
            ),
            (
                "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
                None
            ),
            (
                "git+ssh://bitbucket.org/foo/bar#branch",
                Some("https://bitbucket.org/foo/bar/get/branch.tar.gz")
            ),
            (
                "ssh://git@gitlab.com/foo/bar.git#fix/bug",
                Some("https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=fix/bug")
            ),
            (
                "git+ssh://git.sr.ht/~foo/bar#branch",
                Some("https://git.sr.ht/~foo/bar/archive/branch.tar.gz")
            ),
        ] {
            assert_eq!(
                get_hosted_git_url(&Url::parse(input).unwrap()),
                expected.map(|u| Url::parse(u).unwrap())
            );
        }
    }

    #[test]
    fn ideal_hashes() {
        for (input, expected) in [
            ("sha512-foo sha1-bar", Some("sha512-foo")),
            ("sha1-bar md5-foo", Some("sha1-bar")),
            ("sha1-bar", Some("sha1-bar")),
            ("sha512-foo", Some("sha512-foo")),
            ("foo-bar sha1-bar", Some("sha1-bar")),
            ("foo-bar baz-foo", None),
        ] {
            assert_eq!(get_ideal_hash(input).ok(), expected);
        }
    }

    #[test]
    fn git_shorthand_v1() -> anyhow::Result<()> {
        let old =
            {
                let mut o = HashMap::new();
                o.insert(
                String::from("sqlite3"),
                OldPackage {
                    version: UrlOrString::Url(Url::parse(
                        "github:mapbox/node-sqlite3#593c9d498be2510d286349134537e3bf89401c4a",
                    ).unwrap()),
                    bundled: false,
                    resolved: None,
                    integrity: None,
                    dependencies: None,
                },
            );
                o
            };

        let initial_url = get_initial_url()?;

        let new = to_new_packages(old, &initial_url)?;

        assert_eq!(new.len(), 1, "new packages map should contain 1 value");
        assert_eq!(new.into_values().next().unwrap(), Package {
            resolved: Some(UrlOrString::Url(Url::parse("git+ssh://git@github.com/mapbox/node-sqlite3.git#593c9d498be2510d286349134537e3bf89401c4a").unwrap())),
            integrity: None
        });

        Ok(())
    }
}
<|endoftext|>


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

#![crate_name="mathx"]
#![crate_type="dylib"]

extern crate libc;
extern crate lua;

// import low level lua_State
use lua::ffi::lua_State;

// import high level lua_State wrapper and a type we'll need for library
// registration
use lua::{State, Function};

// import c_int since we need this to interface with Lua
use libc::c_int;

// sin/cos/tan
use std::num::FloatMath;

// simple binding to Rust's sin function
#[allow(non_snake_case)]
extern "C" fn sin(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  // convert the value on top of the stack to a number
  let num = state.to_number(-1);
  // push the sine of that number onto the stack
  state.push_number(num.sin());
  // return one value to Lua
  1
}

// simple binding to Rust's cos function
#[allow(non_snake_case)]
extern "C" fn cos(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.cos());
  1
}

// simple binding to Rust's tan function
#[allow(non_snake_case)]
extern "C" fn tan(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.tan());
  1
}

// mapping of function name to function pointer
const MATHX_LIB: [(&'static str, Function), ..3] = [
  ("sin", Some(sin)),
  ("cos", Some(cos)),
  ("tan", Some(tan)),
];

// the format of this function name is defined by the Lua manual; the Lua
// interpreter will call into this when you require() this library
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn luaopen_mathx(L: *mut lua_State) -> c_int {
  // construct a state wrapper object from the pointer we were given
  let mut state = State::from_ptr(L);
  // create a new table and set fields for each function defined in MATHX_LIB
  state.new_lib(MATHX_LIB);
  // return it on the stack
  1
}

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

#![crate_name="mathx"]
#![crate_type="dylib"]

extern crate libc;
extern crate lua;

// import low level lua_State
use lua::ffi::lua_State;

// import high level lua_State wrapper and a type we'll need for library
// registration
use lua::{State, Function};

// import c_int since we need this to interface with Lua
use libc::c_int;

// sin/cos/tan
use std::num::FloatMath;

// simple binding to Rust's sin function
#[allow(non_snake_case)]
extern "C" fn sin(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  // convert the value on top of the stack to a number
  let num = state.to_number(-1);
  // push the sine of that number onto the stack
  state.push_number(num.sin());
  // return one value to Lua
  1
}

// simple binding to Rust's cos function
#[allow(non_snake_case)]
extern "C" fn cos(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.cos());
  1
}

// simple binding to Rust's tan function
#[allow(non_snake_case)]
extern "C" fn tan(L: *mut lua_State) -> c_int {
  let mut state = State::from_ptr(L);
  let num = state.to_number(-1);
  state.push_number(num.tan());
  1
}

// mapping of function name to function pointer
const MATHX_LIB: [(&'static str, Function), ..3] = [
  ("sin", Some(sin)),
  ("cos", Some(cos)),
  ("tan", Some(tan)),
];

// the format of this function name is defined by the Lua manual; the Lua
// interpreter will call into this when you require() this library
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn luaopen_mathx(L: *mut lua_State) -> c_int {
  // construct a state wrapper object from the pointer we were given
  let mut state = State::from_ptr(L);
  // create a new table and set fields for each function defined in MATHX_LIB
  state.new_lib(&MATHX_LIB);
  // return it on the stack
  1
}

<|endoftext|>


<commit_before>use codec::{
    eyre::{bail, Result},
    stencila_schema::Node,
    Codec, CodecTrait,
};
use codec_format::FormatCodec;
use formats::FORMATS;
use once_cell::sync::Lazy;
use std::{collections::BTreeMap, path::Path, sync::Arc};

// Re-exports for convenience elsewhere
pub use codec::{DecodeOptions, EncodeOptions};

// The following high level functions hide the implementation
// detail of having a static list of codecs. They are intended as the
// only public interface for this crate.

pub async fn from_str(content: &str, format: &str, options: Option<DecodeOptions>) -> Result<Node> {
    CODECS.from_str(content, format, options).await
}

pub async fn from_path<T: AsRef<Path>>(
    path: &T,
    format: &str,
    options: Option<DecodeOptions>,
) -> Result<Node>
where
    T: Send + Sync,
{
    CODECS.from_path(path, format, options).await
}

pub async fn to_string(
    node: &Node,
    format: &str,
    options: Option<EncodeOptions>,
) -> Result<String> {
    CODECS.to_string(node, format, options).await
}

pub async fn to_path<T: AsRef<Path>>(
    node: &Node,
    path: &T,
    format: &str,
    options: Option<EncodeOptions>,
) -> Result<()>
where
    T: Send + Sync,
{
    CODECS.to_path(node, path, format, options).await
}

/// The set of registered codecs in the current process
static CODECS: Lazy<Arc<Codecs>> = Lazy::new(|| Arc::new(Codecs::new()));

/// A set of registered codecs, either built-in, or provided by plugins
struct Codecs {
    inner: BTreeMap<String, Codec>,
}

/// A macro to dispatch methods to builtin codecs
///
/// This avoids having to do a search over the codecs's specs for matching
/// `formats`.
macro_rules! dispatch_builtins {
    ($var:expr, $method:ident $(,$arg:expr)*) => {
        match $var.as_str() {
            #[cfg(feature = "date")]
            "date" => Some(codec_date::DateCodec::$method($($arg),*)),
            #[cfg(feature = "docx")]
            "docx" => Some(codec_docx::DocxCodec::$method($($arg),*)),
            #[cfg(feature = "html")]
            "html" => Some(codec_html::HtmlCodec::$method($($arg),*)),
            #[cfg(feature = "ipynb")]
            "ipynb" => Some(codec_ipynb::IpynbCodec::$method($($arg),*)),
            #[cfg(feature = "json")]
            "json" => Some(codec_json::JsonCodec::$method($($arg),*)),
            #[cfg(feature = "json5")]
            "json5" => Some(codec_json5::Json5Codec::$method($($arg),*)),
            #[cfg(feature = "latex")]
            "latex" => Some(codec_latex::LatexCodec::$method($($arg),*)),
            #[cfg(feature = "pandoc")]
            "pandoc" => Some(codec_pandoc::PandocCodec::$method($($arg),*)),
            #[cfg(feature = "person")]
            "person" => Some(codec_person::PersonCodec::$method($($arg),*)),
            #[cfg(feature = "md")]
            "md" => Some(codec_md::MdCodec::$method($($arg),*)),
            #[cfg(feature = "rmd")]
            "rmd" => Some(codec_rmd::RmdCodec::$method($($arg),*)),
            #[cfg(feature = "rpng")]
            "rpng" => Some(codec_rpng::RpngCodec::$method($($arg),*)),
            #[cfg(feature = "toml")]
            "toml" => Some(codec_toml::TomlCodec::$method($($arg),*)),
            #[cfg(feature = "txt")]
            "txt" => Some(codec_txt::TxtCodec::$method($($arg),*)),
            #[cfg(feature = "yaml")]
            "yaml" => Some(codec_yaml::YamlCodec::$method($($arg),*)),

            _ => None
        }
    };
}

impl Codecs {
    /// Create a new codec registry
    pub fn new() -> Self {
        let inner = vec![
            #[cfg(feature = "date")]
            ("date", codec_date::DateCodec::spec()),
            #[cfg(feature = "docx")]
            ("docx", codec_docx::DocxCodec::spec()),
            #[cfg(feature = "html")]
            ("html", codec_html::HtmlCodec::spec()),
            #[cfg(feature = "ipynb")]
            ("ipynb", codec_ipynb::IpynbCodec::spec()),
            #[cfg(feature = "json")]
            ("json", codec_json::JsonCodec::spec()),
            #[cfg(feature = "json5")]
            ("json5", codec_json5::Json5Codec::spec()),
            #[cfg(feature = "latex")]
            ("latex", codec_latex::LatexCodec::spec()),
            #[cfg(feature = "pandoc")]
            ("pandoc", codec_pandoc::PandocCodec::spec()),
            #[cfg(feature = "person")]
            ("person", codec_person::PersonCodec::spec()),
            #[cfg(feature = "md")]
            ("md", codec_md::MdCodec::spec()),
            #[cfg(feature = "rmd")]
            ("rmd", codec_rmd::RmdCodec::spec()),
            #[cfg(feature = "rpng")]
            ("rpng", codec_rpng::RpngCodec::spec()),
            #[cfg(feature = "toml")]
            ("toml", codec_toml::TomlCodec::spec()),
            #[cfg(feature = "txt")]
            ("txt", codec_txt::TxtCodec::spec()),
            #[cfg(feature = "yaml")]
            ("yaml", codec_yaml::YamlCodec::spec()),
        ]
        .into_iter()
        .map(|(label, codec)| (label.to_string(), codec))
        .collect();

        Self { inner }
    }

    /// List all the codecs
    fn list(&self) -> Vec<String> {
        self.inner
            .keys()
            .into_iter()
            .cloned()
            .collect::<Vec<String>>()
    }

    /// Get the codec with the given id
    fn get(&self, label: &str) -> Result<Codec> {
        match self.inner.get(label) {
            Some(codec) => Ok(codec.clone()),
            None => bail!("No codec with label `{}`", label),
        }
    }

    #[allow(clippy::needless_update)]
    async fn from_str(
        &self,
        content: &str,
        format: &str,
        options: Option<DecodeOptions>,
    ) -> Result<Node> {
        let format = FORMATS.match_name(format);
        let options = Some(DecodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) =
            dispatch_builtins!(format.name, from_str_async, content, options.clone())
        {
            return future.await;
        }

        if let Ok(node) = FormatCodec::from_str_async(content, options).await {
            return Ok(node);
        }

        bail!(
            "Unable to decode node from string with format `{}`: no matching codec found",
            format.name
        )
    }

    #[allow(clippy::needless_update)]
    async fn from_path<T: AsRef<Path>>(
        &self,
        path: &T,
        format: &str,
        options: Option<DecodeOptions>,
    ) -> Result<Node>
    where
        T: Send + Sync,
    {
        let format = FORMATS.match_name(format);
        let options = Some(DecodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, from_path, path, options) {
            return future.await;
        }

        bail!(
            "Unable to decode node from path with format `{}`: no matching codec found",
            format.name
        )
    }

    #[allow(unused_variables)]
    async fn to_string(
        &self,
        node: &Node,
        format: &str,
        options: Option<EncodeOptions>,
    ) -> Result<String> {
        let format = FORMATS.match_name(format);
        let options = Some(EncodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, to_string_async, node, options) {
            return future.await;
        }

        bail!(
            "Unable to encode node to string of format `{}`: no matching codec found",
            format.name
        )
    }

    async fn to_path<T: AsRef<Path>>(
        &self,
        node: &Node,
        path: &T,
        format: &str,
        options: Option<EncodeOptions>,
    ) -> Result<()>
    where
        T: Send + Sync,
    {
        let format = FORMATS.match_name(format);
        let options = Some(EncodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, to_path, node, path, options) {
            return future.await;
        }

        bail!(
            "Unable to encode node to path of format `{}`: no matching codec found",
            format.name
        )
    }
}

impl Default for Codecs {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "cli")]
pub mod commands {
    use super::*;
    use cli_utils::{result, Result, Run};
    use codec::async_trait::async_trait;
    use structopt::StructOpt;

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Manage codecs",
        setting = structopt::clap::AppSettings::ColoredHelp,
        setting = structopt::clap::AppSettings::VersionlessSubcommands
    )]
    pub struct Command {
        #[structopt(subcommand)]
        pub action: Action,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::DeriveDisplayOrder
    )]
    pub enum Action {
        List(List),
        Show(Show),
    }

    #[async_trait]
    impl Run for Command {
        async fn run(&self) -> Result {
            let Self { action } = self;
            match action {
                Action::List(action) => action.run().await,
                Action::Show(action) => action.run().await,
            }
        }
    }

    /// List the codecs that are available
    ///
    /// The list of available codecs includes those that are built into the Stencila
    /// binary (e.g. `html`) as well as any codecs provided by plugins.
    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct List {}
    #[async_trait]
    impl Run for List {
        async fn run(&self) -> Result {
            let list = CODECS.list();
            result::value(list)
        }
    }

    /// Show the specifications of a codec
    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Show {
        /// The label of the codec
        ///
        /// To get the list of codec labels use `stencila codecs list`.
        label: String,
    }
    impl Show {
        pub async fn run(&self) -> Result {
            let codec = CODECS.get(&self.label)?;
            result::value(codec)
        }
    }
}
<commit_msg>docs(Codecs): Add and clean up documentation<commit_after>use codec::{
    eyre::{bail, Result},
    stencila_schema::Node,
    Codec, CodecTrait,
};
use codec_format::FormatCodec;
use formats::FORMATS;
use once_cell::sync::Lazy;
use std::{collections::BTreeMap, path::Path, sync::Arc};

// Re-exports for use in other crates that call the following functions
pub use codec::{DecodeOptions, EncodeOptions};

// The following high level functions hide the implementation
// detail of having a static list of codecs. They are intended as the
// only public interface for this crate.

/// Decode a document node from a string
pub async fn from_str(content: &str, format: &str, options: Option<DecodeOptions>) -> Result<Node> {
    CODECS.from_str(content, format, options).await
}

/// Decode a document node from a file system path
pub async fn from_path<T: AsRef<Path>>(
    path: &T,
    format: &str,
    options: Option<DecodeOptions>,
) -> Result<Node>
where
    T: Send + Sync,
{
    CODECS.from_path(path, format, options).await
}

/// Encode a document node to a string
pub async fn to_string(
    node: &Node,
    format: &str,
    options: Option<EncodeOptions>,
) -> Result<String> {
    CODECS.to_string(node, format, options).await
}

/// Encode a document node to a file system path
pub async fn to_path<T: AsRef<Path>>(
    node: &Node,
    path: &T,
    format: &str,
    options: Option<EncodeOptions>,
) -> Result<()>
where
    T: Send + Sync,
{
    CODECS.to_path(node, path, format, options).await
}

/// The set of registered codecs in the current process
static CODECS: Lazy<Arc<Codecs>> = Lazy::new(|| Arc::new(Codecs::new()));

/// A set of registered codecs, either built-in, or provided by plugins
struct Codecs {
    inner: BTreeMap<String, Codec>,
}

/// A macro to dispatch methods to builtin codecs
///
/// This avoids having to do a search over the codecs's specs for matching
/// `formats`.
macro_rules! dispatch_builtins {
    ($var:expr, $method:ident $(,$arg:expr)*) => {
        match $var.as_str() {
            #[cfg(feature = "date")]
            "date" => Some(codec_date::DateCodec::$method($($arg),*)),
            #[cfg(feature = "docx")]
            "docx" => Some(codec_docx::DocxCodec::$method($($arg),*)),
            #[cfg(feature = "html")]
            "html" => Some(codec_html::HtmlCodec::$method($($arg),*)),
            #[cfg(feature = "ipynb")]
            "ipynb" => Some(codec_ipynb::IpynbCodec::$method($($arg),*)),
            #[cfg(feature = "json")]
            "json" => Some(codec_json::JsonCodec::$method($($arg),*)),
            #[cfg(feature = "json5")]
            "json5" => Some(codec_json5::Json5Codec::$method($($arg),*)),
            #[cfg(feature = "latex")]
            "latex" => Some(codec_latex::LatexCodec::$method($($arg),*)),
            #[cfg(feature = "pandoc")]
            "pandoc" => Some(codec_pandoc::PandocCodec::$method($($arg),*)),
            #[cfg(feature = "person")]
            "person" => Some(codec_person::PersonCodec::$method($($arg),*)),
            #[cfg(feature = "md")]
            "md" => Some(codec_md::MdCodec::$method($($arg),*)),
            #[cfg(feature = "rmd")]
            "rmd" => Some(codec_rmd::RmdCodec::$method($($arg),*)),
            #[cfg(feature = "rpng")]
            "rpng" => Some(codec_rpng::RpngCodec::$method($($arg),*)),
            #[cfg(feature = "toml")]
            "toml" => Some(codec_toml::TomlCodec::$method($($arg),*)),
            #[cfg(feature = "txt")]
            "txt" => Some(codec_txt::TxtCodec::$method($($arg),*)),
            #[cfg(feature = "yaml")]
            "yaml" => Some(codec_yaml::YamlCodec::$method($($arg),*)),

            _ => None
        }
    };
}

impl Codecs {
    /// Create a new codec registry
    pub fn new() -> Self {
        let inner = vec![
            #[cfg(feature = "date")]
            ("date", codec_date::DateCodec::spec()),
            #[cfg(feature = "docx")]
            ("docx", codec_docx::DocxCodec::spec()),
            #[cfg(feature = "html")]
            ("html", codec_html::HtmlCodec::spec()),
            #[cfg(feature = "ipynb")]
            ("ipynb", codec_ipynb::IpynbCodec::spec()),
            #[cfg(feature = "json")]
            ("json", codec_json::JsonCodec::spec()),
            #[cfg(feature = "json5")]
            ("json5", codec_json5::Json5Codec::spec()),
            #[cfg(feature = "latex")]
            ("latex", codec_latex::LatexCodec::spec()),
            #[cfg(feature = "pandoc")]
            ("pandoc", codec_pandoc::PandocCodec::spec()),
            #[cfg(feature = "person")]
            ("person", codec_person::PersonCodec::spec()),
            #[cfg(feature = "md")]
            ("md", codec_md::MdCodec::spec()),
            #[cfg(feature = "rmd")]
            ("rmd", codec_rmd::RmdCodec::spec()),
            #[cfg(feature = "rpng")]
            ("rpng", codec_rpng::RpngCodec::spec()),
            #[cfg(feature = "toml")]
            ("toml", codec_toml::TomlCodec::spec()),
            #[cfg(feature = "txt")]
            ("txt", codec_txt::TxtCodec::spec()),
            #[cfg(feature = "yaml")]
            ("yaml", codec_yaml::YamlCodec::spec()),
        ]
        .into_iter()
        .map(|(label, codec)| (label.to_string(), codec))
        .collect();

        Self { inner }
    }

    /// List all the codecs
    fn list(&self) -> Vec<String> {
        self.inner
            .keys()
            .into_iter()
            .cloned()
            .collect::<Vec<String>>()
    }

    /// Get the codec with the given id
    fn get(&self, label: &str) -> Result<Codec> {
        match self.inner.get(label) {
            Some(codec) => Ok(codec.clone()),
            None => bail!("No codec with label `{}`", label),
        }
    }

    /// Decode a document node from a string
    #[allow(clippy::needless_update)]
    async fn from_str(
        &self,
        content: &str,
        format: &str,
        options: Option<DecodeOptions>,
    ) -> Result<Node> {
        let format = FORMATS.match_name(format);
        let options = Some(DecodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) =
            dispatch_builtins!(format.name, from_str_async, content, options.clone())
        {
            return future.await;
        }

        if let Ok(node) = FormatCodec::from_str_async(content, options).await {
            return Ok(node);
        }

        bail!(
            "Unable to decode node from string with format `{}`: no matching codec found",
            format.name
        )
    }

    /// Decode a document node from a file system path
    #[allow(clippy::needless_update)]
    async fn from_path<T: AsRef<Path>>(
        &self,
        path: &T,
        format: &str,
        options: Option<DecodeOptions>,
    ) -> Result<Node>
    where
        T: Send + Sync,
    {
        let format = FORMATS.match_name(format);
        let options = Some(DecodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, from_path, path, options) {
            return future.await;
        }

        bail!(
            "Unable to decode node from path with format `{}`: no matching codec found",
            format.name
        )
    }

    /// Encode a document node to a string
    #[allow(unused_variables)]
    async fn to_string(
        &self,
        node: &Node,
        format: &str,
        options: Option<EncodeOptions>,
    ) -> Result<String> {
        let format = FORMATS.match_name(format);
        let options = Some(EncodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, to_string_async, node, options) {
            return future.await;
        }

        bail!(
            "Unable to encode node to string of format `{}`: no matching codec found",
            format.name
        )
    }

    /// Encode a document node to a file system path
    async fn to_path<T: AsRef<Path>>(
        &self,
        node: &Node,
        path: &T,
        format: &str,
        options: Option<EncodeOptions>,
    ) -> Result<()>
    where
        T: Send + Sync,
    {
        let format = FORMATS.match_name(format);
        let options = Some(EncodeOptions {
            format: Some(format.name.clone()),
            ..options.unwrap_or_default()
        });

        if let Some(future) = dispatch_builtins!(format.name, to_path, node, path, options) {
            return future.await;
        }

        bail!(
            "Unable to encode node to path of format `{}`: no matching codec found",
            format.name
        )
    }
}

impl Default for Codecs {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "cli")]
pub mod commands {
    use super::*;
    use cli_utils::{result, Result, Run};
    use codec::async_trait::async_trait;
    use structopt::StructOpt;

    /// Manage codecs
    ///
    /// In Stencila, a "codec" is responsible for converting documents
    /// to ("encoding") and from ("decoding") a format (e.g. Markdown).
    /// This command alls you to list the available codecs and see their
    /// specifications (e.g. which formats they support).
    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::ColoredHelp,
        setting = structopt::clap::AppSettings::VersionlessSubcommands
    )]
    pub struct Command {
        #[structopt(subcommand)]
        pub action: Action,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::DeriveDisplayOrder
    )]
    pub enum Action {
        List(List),
        Show(Show),
    }

    #[async_trait]
    impl Run for Command {
        async fn run(&self) -> Result {
            let Self { action } = self;
            match action {
                Action::List(action) => action.run().await,
                Action::Show(action) => action.run().await,
            }
        }
    }

    /// List the codecs available
    ///
    /// The list of available codecs includes those that are built into the Stencila
    /// binary (e.g. `html`) as well as any codecs provided by plugins.
    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct List {}

    #[async_trait]
    impl Run for List {
        async fn run(&self) -> Result {
            let list = CODECS.list();
            result::value(list)
        }
    }

    /// Show the specifications of a codec
    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Show {
        /// The label of the codec
        ///
        /// To get the list of codec labels use `stencila codecs list`.
        label: String,
    }

    #[async_trait]
    impl Run for Show {
        async fn run(&self) -> Result {
            let codec = CODECS.get(&self.label)?;
            result::value(codec)
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate enum_map;

use enum_map::{EnumMap, IntoIter};

use std::cell::RefCell;
use std::collections::HashSet;

#[derive(Copy, Clone, Debug, EnumMap, PartialEq)]
enum Example {
    A,
    B,
    C,
}

#[test]
fn test_bool() {
    let mut map = enum_map! { false => 24, true => 42 };
    assert_eq!(map[false], 24);
    assert_eq!(map[true], 42);
    map[false] += 1;
    assert_eq!(map[false], 25);
    for (key, item) in &mut map {
        if !key {
            *item += 1;
        }
    }
    assert_eq!(map[false], 26);
    assert_eq!(map[true], 42);
}

#[test]
fn test_option_bool() {
    let mut map = enum_map! { None => 1, Some(false) => 2, Some(true) => 3};
    assert_eq!(map[None], 1);
    assert_eq!(map[Some(false)], 2);
    assert_eq!(map[Some(true)], 3);
    map[None] = 4;
    map[Some(false)] = 5;
    map[Some(true)] = 6;
    assert_eq!(map[None], 4);
    assert_eq!(map[Some(false)], 5);
    assert_eq!(map[Some(true)], 6);
    assert_eq!(map.as_slice(), [4, 5, 6]);
    assert_eq!(
        map.into_iter().collect::<Vec<_>>(),
        [(None, 4), (Some(false), 5), (Some(true), 6)]
    );
}

#[test]
fn test_clone() {
    let map = enum_map! { false => 3, true => 5 };
    assert_eq!(map.clone(), map);
}

#[test]
fn test_debug() {
    let map = enum_map! { false => 3, true => 5 };
    assert_eq!(format!("{:?}", map), "{false: 3, true: 5}");
}

#[test]
fn test_hash() {
    let map = enum_map! { false => 3, true => 5 };
    let mut set = HashSet::new();
    set.insert(map);
    assert!(set.contains(&map));
}

#[test]
fn discriminants() {
    #[derive(Debug, EnumMap, PartialEq)]
    enum Discriminants {
        A = 2000,
        B = 3000,
        C = 1000,
    }
    let mut map = EnumMap::new();
    map[Discriminants::A] = 3;
    map[Discriminants::B] = 2;
    map[Discriminants::C] = 1;
    let mut pairs = map.iter();
    assert_eq!(pairs.next(), Some((Discriminants::A, &3)));
    assert_eq!(pairs.next(), Some((Discriminants::B, &2)));
    assert_eq!(pairs.next(), Some((Discriminants::C, &1)));
    assert_eq!(pairs.next(), None);
}

#[test]
fn extend() {
    let mut map = enum_map! { _ => 0 };
    map.extend(vec![(Example::A, 3)]);
    map.extend(vec![(&Example::B, &4)]);
    assert_eq!(
        map,
        enum_map! { Example:: A => 3, Example:: B => 4, Example::C => 0 }
    );
}

#[test]
fn huge_enum() {
    #[derive(EnumMap)]
    enum Example {
        A,
        B,
        C,
        D,
        E,
        F,
        G,
        H,
        I,
        J,
        K,
        L,
        M,
        N,
        O,
        P,
        Q,
        R,
        S,
        T,
        U,
        V,
        W,
        X,
        Y,
        Z,
        Aa,
        Bb,
        Cc,
        Dd,
        Ee,
        Ff,
        Gg,
        Hh,
        Ii,
        Jj,
        Kk,
        Ll,
        Mm,
        Nn,
        Oo,
        Pp,
        Qq,
        Rr,
        Ss,
        Tt,
        Uu,
        Vv,
        Ww,
        Xx,
        Yy,
        Zz,
    }

    let map = enum_map! { _ => 2 };
    assert_eq!(map[Example::Xx], 2);
}

#[test]
fn iterator_len() {
    assert_eq!(
        enum_map! { Example::A | Example::B | Example::C => 0 }
            .iter()
            .len(),
        3
    );
}

#[test]
fn iter_mut_len() {
    assert_eq!(
        enum_map! { Example::A | Example::B | Example::C => 0 }
            .iter_mut()
            .len(),
        3
    );
}

#[test]
fn iterator_next_back() {
    assert_eq!(
        enum_map! { Example::A => 1, Example::B => 2, Example::C => 3 }
            .iter()
            .next_back(),
        Some((Example::C, &3))
    );
}

#[test]
fn iter_mut_next_back() {
    assert_eq!(
        enum_map! { Example::A => 1, Example::B => 2, Example::C => 3 }
            .iter()
            .next_back(),
        Some((Example::C, &mut 3))
    );
}

#[test]
fn into_iter() {
    let mut iter = enum_map! { true => 5, false => 7 }.into_iter();
    assert_eq!(iter.next(), Some((false, 7)));
    assert_eq!(iter.next(), Some((true, 5)));
    assert_eq!(iter.next(), None);
    assert_eq!(iter.next(), None);
}

struct DropReporter<'a> {
    into: &'a RefCell<Vec<usize>>,
    value: usize,
}

impl<'a> Drop for DropReporter<'a> {
    fn drop(&mut self) {
        self.into.borrow_mut().push(self.value);
    }
}

#[test]
fn into_iter_drop() {
    let dropped = RefCell::new(Vec::new());
    let mut a: IntoIter<Example, _> = enum_map! {
        k => DropReporter {
            into: &dropped,
            value: k as usize,
        },
    }.into_iter();
    assert_eq!(a.next().unwrap().0, Example::A);
    assert_eq!(*dropped.borrow(), &[0]);
    drop(a);
    assert_eq!(*dropped.borrow(), &[0, 1, 2]);
}

#[test]
fn test_u8() {
    let map = enum_map! { b'a' => 4, _ => 0 };
    assert_eq!(map[b'a'], 4);
    assert_eq!(map[b'b'], 0);
}

#[derive(EnumMap)]
enum Void {}

#[test]
fn empty_map() {
    let void: EnumMap<Void, Void> = enum_map!{};
    assert!(void.is_empty());
}

#[test]
#[should_panic]
fn empty_value() {
    let _void: EnumMap<bool, Void> = enum_map! { _ => unreachable!() };
}
<commit_msg>Test writing to enum map of u8<commit_after>#[macro_use]
extern crate enum_map;

use enum_map::{EnumMap, IntoIter};

use std::cell::RefCell;
use std::collections::HashSet;

#[derive(Copy, Clone, Debug, EnumMap, PartialEq)]
enum Example {
    A,
    B,
    C,
}

#[test]
fn test_bool() {
    let mut map = enum_map! { false => 24, true => 42 };
    assert_eq!(map[false], 24);
    assert_eq!(map[true], 42);
    map[false] += 1;
    assert_eq!(map[false], 25);
    for (key, item) in &mut map {
        if !key {
            *item += 1;
        }
    }
    assert_eq!(map[false], 26);
    assert_eq!(map[true], 42);
}

#[test]
fn test_option_bool() {
    let mut map = enum_map! { None => 1, Some(false) => 2, Some(true) => 3};
    assert_eq!(map[None], 1);
    assert_eq!(map[Some(false)], 2);
    assert_eq!(map[Some(true)], 3);
    map[None] = 4;
    map[Some(false)] = 5;
    map[Some(true)] = 6;
    assert_eq!(map[None], 4);
    assert_eq!(map[Some(false)], 5);
    assert_eq!(map[Some(true)], 6);
    assert_eq!(map.as_slice(), [4, 5, 6]);
    assert_eq!(
        map.into_iter().collect::<Vec<_>>(),
        [(None, 4), (Some(false), 5), (Some(true), 6)]
    );
}

#[test]
fn test_clone() {
    let map = enum_map! { false => 3, true => 5 };
    assert_eq!(map.clone(), map);
}

#[test]
fn test_debug() {
    let map = enum_map! { false => 3, true => 5 };
    assert_eq!(format!("{:?}", map), "{false: 3, true: 5}");
}

#[test]
fn test_hash() {
    let map = enum_map! { false => 3, true => 5 };
    let mut set = HashSet::new();
    set.insert(map);
    assert!(set.contains(&map));
}

#[test]
fn discriminants() {
    #[derive(Debug, EnumMap, PartialEq)]
    enum Discriminants {
        A = 2000,
        B = 3000,
        C = 1000,
    }
    let mut map = EnumMap::new();
    map[Discriminants::A] = 3;
    map[Discriminants::B] = 2;
    map[Discriminants::C] = 1;
    let mut pairs = map.iter();
    assert_eq!(pairs.next(), Some((Discriminants::A, &3)));
    assert_eq!(pairs.next(), Some((Discriminants::B, &2)));
    assert_eq!(pairs.next(), Some((Discriminants::C, &1)));
    assert_eq!(pairs.next(), None);
}

#[test]
fn extend() {
    let mut map = enum_map! { _ => 0 };
    map.extend(vec![(Example::A, 3)]);
    map.extend(vec![(&Example::B, &4)]);
    assert_eq!(
        map,
        enum_map! { Example:: A => 3, Example:: B => 4, Example::C => 0 }
    );
}

#[test]
fn huge_enum() {
    #[derive(EnumMap)]
    enum Example {
        A,
        B,
        C,
        D,
        E,
        F,
        G,
        H,
        I,
        J,
        K,
        L,
        M,
        N,
        O,
        P,
        Q,
        R,
        S,
        T,
        U,
        V,
        W,
        X,
        Y,
        Z,
        Aa,
        Bb,
        Cc,
        Dd,
        Ee,
        Ff,
        Gg,
        Hh,
        Ii,
        Jj,
        Kk,
        Ll,
        Mm,
        Nn,
        Oo,
        Pp,
        Qq,
        Rr,
        Ss,
        Tt,
        Uu,
        Vv,
        Ww,
        Xx,
        Yy,
        Zz,
    }

    let map = enum_map! { _ => 2 };
    assert_eq!(map[Example::Xx], 2);
}

#[test]
fn iterator_len() {
    assert_eq!(
        enum_map! { Example::A | Example::B | Example::C => 0 }
            .iter()
            .len(),
        3
    );
}

#[test]
fn iter_mut_len() {
    assert_eq!(
        enum_map! { Example::A | Example::B | Example::C => 0 }
            .iter_mut()
            .len(),
        3
    );
}

#[test]
fn iterator_next_back() {
    assert_eq!(
        enum_map! { Example::A => 1, Example::B => 2, Example::C => 3 }
            .iter()
            .next_back(),
        Some((Example::C, &3))
    );
}

#[test]
fn iter_mut_next_back() {
    assert_eq!(
        enum_map! { Example::A => 1, Example::B => 2, Example::C => 3 }
            .iter()
            .next_back(),
        Some((Example::C, &mut 3))
    );
}

#[test]
fn into_iter() {
    let mut iter = enum_map! { true => 5, false => 7 }.into_iter();
    assert_eq!(iter.next(), Some((false, 7)));
    assert_eq!(iter.next(), Some((true, 5)));
    assert_eq!(iter.next(), None);
    assert_eq!(iter.next(), None);
}

struct DropReporter<'a> {
    into: &'a RefCell<Vec<usize>>,
    value: usize,
}

impl<'a> Drop for DropReporter<'a> {
    fn drop(&mut self) {
        self.into.borrow_mut().push(self.value);
    }
}

#[test]
fn into_iter_drop() {
    let dropped = RefCell::new(Vec::new());
    let mut a: IntoIter<Example, _> = enum_map! {
        k => DropReporter {
            into: &dropped,
            value: k as usize,
        },
    }.into_iter();
    assert_eq!(a.next().unwrap().0, Example::A);
    assert_eq!(*dropped.borrow(), &[0]);
    drop(a);
    assert_eq!(*dropped.borrow(), &[0, 1, 2]);
}

#[test]
fn test_u8() {
    let mut map = enum_map! { b'a' => 4, _ => 0 };
    map[b'c'] = 3;
    assert_eq!(map[b'a'], 4);
    assert_eq!(map[b'b'], 0);
    assert_eq!(map[b'c'], 3);
}

#[derive(EnumMap)]
enum Void {}

#[test]
fn empty_map() {
    let void: EnumMap<Void, Void> = enum_map!{};
    assert!(void.is_empty());
}

#[test]
#[should_panic]
fn empty_value() {
    let _void: EnumMap<bool, Void> = enum_map! { _ => unreachable!() };
}
<|endoftext|>


<commit_before>use std::fmt::Display;

use im::HashMap;

use crate::compiler::hir::Id;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Value {
    Int(u64),
    Text(String),
    Symbol(String),
    Lambda(Lambda),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Environment {
    parent: Option<Box<Environment>>,
    bindings: HashMap<Id, Value>,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Lambda {
    pub captured_environment: Environment,
    pub id: Id,
}

impl Value {
    pub fn nothing() -> Self {
        Value::Symbol("Nothing".to_owned())
    }
    pub fn bool_true() -> Self {
        Value::Symbol("True".to_owned())
    }
    pub fn bool_false() -> Self {
        Value::Symbol("False".to_owned())
    }
    pub fn argument_count_mismatch_text(
        function_name: &str,
        parameter_count: usize,
        argument_count: usize,
    ) -> Value {
        format!(
            "Function `{}` expects {} arguments, but {} were given.",
            function_name, parameter_count, argument_count
        )
        .into()
    }
}
impl From<u64> for Value {
    fn from(value: u64) -> Self {
        Value::Int(value)
    }
}
impl From<String> for Value {
    fn from(value: String) -> Self {
        Value::Text(value)
    }
}
impl From<bool> for Value {
    fn from(value: bool) -> Self {
        if value {
            Value::bool_true()
        } else {
            Value::bool_false()
        }
    }
}
impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Int(value) => write!(f, "{}", value),
            Value::Text(value) => write!(f, "{}", value),
            Value::Symbol(value) => write!(f, "{}", value),
            Value::Lambda(_) => write!(f, "{{ … }}"),
        }
    }
}

impl Environment {
    pub fn new(bindings: HashMap<Id, Value>) -> Environment {
        Self {
            parent: None,
            bindings,
        }
    }
    pub fn store(&mut self, id: Id, value: Value) {
        assert!(
            !self.bindings.contains_key(&id),
            "Tried to overwrite a value at ID {}: {:?}",
            &id,
            &value
        );
        assert!(self.bindings.insert(id, value).is_none())
    }
    pub fn get(&self, id: &Id) -> Option<Value> {
        self.bindings
            .get(id)
            .map(|it| it.clone())
            .or_else(|| self.parent.as_ref()?.get(id))
    }
}
<commit_msg>fix(compiler): add quotes around Value's Display<commit_after>use std::fmt::Display;

use im::HashMap;

use crate::compiler::hir::Id;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Value {
    Int(u64),
    Text(String),
    Symbol(String),
    Lambda(Lambda),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Environment {
    parent: Option<Box<Environment>>,
    bindings: HashMap<Id, Value>,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Lambda {
    pub captured_environment: Environment,
    pub id: Id,
}

impl Value {
    pub fn nothing() -> Self {
        Value::Symbol("Nothing".to_owned())
    }
    pub fn bool_true() -> Self {
        Value::Symbol("True".to_owned())
    }
    pub fn bool_false() -> Self {
        Value::Symbol("False".to_owned())
    }
    pub fn argument_count_mismatch_text(
        function_name: &str,
        parameter_count: usize,
        argument_count: usize,
    ) -> Value {
        format!(
            "Function `{}` expects {} arguments, but {} were given.",
            function_name, parameter_count, argument_count
        )
        .into()
    }
}
impl From<u64> for Value {
    fn from(value: u64) -> Self {
        Value::Int(value)
    }
}
impl From<String> for Value {
    fn from(value: String) -> Self {
        Value::Text(value)
    }
}
impl From<bool> for Value {
    fn from(value: bool) -> Self {
        if value {
            Value::bool_true()
        } else {
            Value::bool_false()
        }
    }
}
impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Int(value) => write!(f, "{}", value),
            Value::Text(value) => write!(f, "\"{}\"", value),
            Value::Symbol(value) => write!(f, "{}", value),
            Value::Lambda(_) => write!(f, "{{ … }}"),
        }
    }
}

impl Environment {
    pub fn new(bindings: HashMap<Id, Value>) -> Environment {
        Self {
            parent: None,
            bindings,
        }
    }
    pub fn store(&mut self, id: Id, value: Value) {
        assert!(
            !self.bindings.contains_key(&id),
            "Tried to overwrite a value at ID {}: {:?}",
            &id,
            &value
        );
        assert!(self.bindings.insert(id, value).is_none())
    }
    pub fn get(&self, id: &Id) -> Option<Value> {
        self.bindings
            .get(id)
            .map(|it| it.clone())
            .or_else(|| self.parent.as_ref()?.get(id))
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate exonum;
extern crate env_logger;
extern crate clap;

use clap::{App, SubCommand, Arg, ArgMatches};

use std::marker::PhantomData;
use std::path::Path;

use exonum::storage::{LevelDB, LevelDBOptions, Map, MapIndex};
use exonum::crypto::{HexValue, Hash};
use exonum::helpers::clap::GenerateCommand;

pub enum BlockchainAction {
    FindTx(String),
}

pub struct BlockchainCommand<'a, 'b>
where
    'a: 'b,
{
    _p: PhantomData<App<'a, 'b>>,
}

impl<'a, 'b> BlockchainCommand<'a, 'b>
where
    'a: 'b,
{
    pub fn new() -> App<'a, 'b> {
        SubCommand::with_name("blockchain")
            .about("Blockchain explorer")
            .arg(
                Arg::with_name("LEVELDB_PATH")
                    .short("d")
                    .long("leveldb-path")
                    .value_name("LEVELDB_PATH")
                    .help("Use leveldb database with the given path")
                    .required(true)
                    .takes_value(true),
            )
            .subcommand(
                SubCommand::with_name("find_tx")
                    .about("Find transaction with given hash")
                    .arg(
                        Arg::with_name("TX_HASH")
                            .help("Transaction hash")
                            .required(true)
                            .index(1),
                    ),
            )
    }

    pub fn leveldb_path(matches: &'a ArgMatches<'a>) -> &'a Path {
        Path::new(matches.value_of("LEVELDB_PATH").unwrap())
    }

    pub fn db(matches: &'a ArgMatches<'a>) -> LevelDB {
        let path = Self::leveldb_path(matches);
        let options = LevelDBOptions::new();
        LevelDB::open(path, options).unwrap()
    }

    pub fn action(matches: &'a ArgMatches<'a>) -> BlockchainAction {
        match matches.subcommand() {
            ("find_tx", Some(matches)) => {
                let tx_hash = matches.value_of("TX_HASH").unwrap();
                BlockchainAction::FindTx(tx_hash.into())
            }
            _ => unreachable!("Wrong subcommand"),
        }
    }

    pub fn execute(matches: &'a ArgMatches<'a>) {
        let storage = Self::db(matches);
        match Self::action(matches) {
            BlockchainAction::FindTx(tx_hash) => {
                let hash = Hash::from_hex(tx_hash).unwrap();
                // TODO add constants to common used prefixes in blockchain
                let txs = MapIndex::new(vec![00], &storage);
                let tx: Vec<u8> = txs.get(&hash).unwrap().unwrap();
                println!("{}", tx.to_hex());
            }
        }
    }
}

fn main() {
    exonum::crypto::init();
    exonum::helpers::init_logger().unwrap();

    let app = App::new("Blockchain control utility")
        .version(env!("CARGO_PKG_VERSION"))
        .author("The Exonum Team <exonum@bitfury.com>")
        .about("Blockchain control utility")
        .subcommand(GenerateCommand::new())
        .subcommand(BlockchainCommand::new());

    let matches = app.get_matches();
    match matches.subcommand() {
        ("generate", Some(matches)) => GenerateCommand::execute(matches),
        ("blockchain", Some(matches)) => BlockchainCommand::execute(matches),
        _ => unreachable!("Wrong subcommand"),
    }
}
<commit_msg>Remove dead old code<commit_after><|endoftext|>