<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{
    conversion::analysis::fun::{
        function_wrapper::RustConversionType, ArgumentAnalysis, ReceiverMutability,
    },
    types::QualifiedName,
};
use indexmap::set::IndexSet as HashSet;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::borrow::Cow;
use syn::{
    parse_quote, punctuated::Punctuated, token::Comma, FnArg, GenericArgument, PatType, Path,
    PathSegment, ReturnType, Type, TypePath, TypeReference,
};

/// Function which can add explicit lifetime parameters to function signatures
/// where necessary, based on analysis of parameters and return types.
/// This is necessary in three cases:
/// 1) where the parameter is a Pin<&mut T>
///    and the return type is some kind of reference - because lifetime elision
///    is not smart enough to see inside a Pin.
/// 2) as a workaround for https://github.com/dtolnay/cxx/issues/1024, where the
///    input parameter is a non-POD type but the output reference is a POD or
///    built-in type
/// 3) Any parameter is any form of reference, and we're returning an `impl New`
///    3a) an 'impl ValueParam' counts as a reference.
pub(crate) fn add_explicit_lifetime_if_necessary<'r>(
    param_details: &[ArgumentAnalysis],
    mut params: Punctuated<FnArg, Comma>,
    ret_type: Cow<'r, ReturnType>,
    non_pod_types: &HashSet<QualifiedName>,
) -> (
    Option<TokenStream>,
    Punctuated<FnArg, Comma>,
    Cow<'r, ReturnType>,
) {
    log::info!("Beforehand it's {:?}", params.to_token_stream().to_string());
    let has_mutable_receiver = param_details.iter().any(|pd| {
        matches!(pd.self_type, Some((_, ReceiverMutability::Mutable)))
            && !pd.is_placement_return_destination
    });

    let any_param_is_reference = param_details.iter().any(|pd| {
        pd.has_lifetime
            || matches!(
                pd.conversion.rust_conversion,
                RustConversionType::FromValueParamToPtr
            )
    });
    let return_type_is_impl = return_type_is_impl(&ret_type);
    let non_pod_ref_param = reference_parameter_is_non_pod_reference(&params, non_pod_types);
    let ret_type_pod = return_type_is_pod_or_known_type_reference(&ret_type, non_pod_types);
    let returning_impl_with_a_reference_param = return_type_is_impl && any_param_is_reference;
    let hits_1024_bug = non_pod_ref_param && ret_type_pod;
    if !(has_mutable_receiver || hits_1024_bug || returning_impl_with_a_reference_param) {
        return (None, params, ret_type);
    }
    let new_return_type = match ret_type.as_ref() {
        ReturnType::Type(rarrow, boxed_type) => match boxed_type.as_ref() {
            Type::Reference(rtr) => {
                let mut new_rtr = rtr.clone();
                new_rtr.lifetime = Some(parse_quote! { 'a });
                Some(ReturnType::Type(
                    *rarrow,
                    Box::new(Type::Reference(new_rtr)),
                ))
            }
            Type::Path(typ) => {
                let mut new_path = typ.clone();
                add_lifetime_to_pinned_reference(&mut new_path.path.segments)
                    .ok()
                    .map(|_| ReturnType::Type(*rarrow, Box::new(Type::Path(new_path))))
            }
            Type::ImplTrait(tyit) => {
                let old_tyit = tyit.to_token_stream();
                Some(parse_quote! {
                    #rarrow #old_tyit + 'a
                })
            }
            _ => None,
        },
        _ => None,
    };

    match new_return_type {
        None => (None, params, ret_type),
        Some(new_return_type) => {
            for mut param in params.iter_mut() {
                match &mut param {
                    FnArg::Typed(PatType { ty, .. }) => match ty.as_mut() {
                        Type::Path(TypePath {
                            path: Path { segments, .. },
                            ..
                        }) => add_lifetime_to_pinned_reference(segments).unwrap_or(()),
                        Type::Reference(tyr) => add_lifetime_to_reference(tyr),
                        Type::ImplTrait(tyit) => add_lifetime_to_impl_trait(tyit),
                        _ => {}
                    },
                    _ => {}
                }
            }

            (Some(quote! { <'a> }), params, Cow::Owned(new_return_type))
        }
    }
}

fn reference_parameter_is_non_pod_reference(
    params: &Punctuated<FnArg, Comma>,
    non_pod_types: &HashSet<QualifiedName>,
) -> bool {
    params.iter().any(|param| match param {
        FnArg::Typed(PatType { ty, .. }) => match ty.as_ref() {
            Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() {
                Type::Path(typ) => {
                    let qn = QualifiedName::from_type_path(typ);
                    non_pod_types.contains(&qn)
                }
                _ => false,
            },
            _ => false,
        },
        _ => false,
    })
}

fn return_type_is_pod_or_known_type_reference(
    ret_type: &ReturnType,
    non_pod_types: &HashSet<QualifiedName>,
) -> bool {
    match ret_type {
        ReturnType::Type(_, boxed_type) => match boxed_type.as_ref() {
            Type::Reference(rtr) => match rtr.elem.as_ref() {
                Type::Path(typ) => {
                    let qn = QualifiedName::from_type_path(typ);
                    !non_pod_types.contains(&qn)
                }
                _ => false,
            },
            _ => false,
        },
        _ => false,
    }
}

fn return_type_is_impl(ret_type: &ReturnType) -> bool {
    matches!(ret_type, ReturnType::Type(_, boxed_type) if matches!(boxed_type.as_ref(), Type::ImplTrait(..)))
}

#[derive(Debug)]
enum AddLifetimeError {
    WasNotPin,
}

fn add_lifetime_to_pinned_reference(
    segments: &mut Punctuated<PathSegment, syn::token::Colon2>,
) -> Result<(), AddLifetimeError> {
    static EXPECTED_SEGMENTS: &[(&str, bool)] = &[
        ("std", false),
        ("pin", false),
        ("Pin", true), // true = act on the arguments of this segment
    ];

    for (seg, (expected_name, act)) in segments.iter_mut().zip(EXPECTED_SEGMENTS.iter()) {
        if seg.ident != expected_name {
            return Err(AddLifetimeError::WasNotPin);
        }
        if *act {
            match &mut seg.arguments {
                syn::PathArguments::AngleBracketed(aba) => match aba.args.iter_mut().next() {
                    Some(GenericArgument::Type(Type::Reference(tyr))) => {
                        add_lifetime_to_reference(tyr);
                    }
                    _ => panic!("Expected generic args with a reference"),
                },
                _ => panic!("Expected angle bracketed args"),
            }
        }
    }
    Ok(())
}

fn add_lifetime_to_reference(tyr: &mut syn::TypeReference) {
    tyr.lifetime = Some(parse_quote! { 'a })
}

fn add_lifetime_to_impl_trait(tyit: &mut syn::TypeImplTrait) {
    tyit.bounds
        .push(syn::TypeParamBound::Lifetime(parse_quote! { 'a }))
}
<commit_msg>Clippy fix.<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{
    conversion::analysis::fun::{
        function_wrapper::RustConversionType, ArgumentAnalysis, ReceiverMutability,
    },
    types::QualifiedName,
};
use indexmap::set::IndexSet as HashSet;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::borrow::Cow;
use syn::{
    parse_quote, punctuated::Punctuated, token::Comma, FnArg, GenericArgument, PatType, Path,
    PathSegment, ReturnType, Type, TypePath, TypeReference,
};

/// Function which can add explicit lifetime parameters to function signatures
/// where necessary, based on analysis of parameters and return types.
/// This is necessary in three cases:
/// 1) where the parameter is a Pin<&mut T>
///    and the return type is some kind of reference - because lifetime elision
///    is not smart enough to see inside a Pin.
/// 2) as a workaround for https://github.com/dtolnay/cxx/issues/1024, where the
///    input parameter is a non-POD type but the output reference is a POD or
///    built-in type
/// 3) Any parameter is any form of reference, and we're returning an `impl New`
///    3a) an 'impl ValueParam' counts as a reference.
pub(crate) fn add_explicit_lifetime_if_necessary<'r>(
    param_details: &[ArgumentAnalysis],
    mut params: Punctuated<FnArg, Comma>,
    ret_type: Cow<'r, ReturnType>,
    non_pod_types: &HashSet<QualifiedName>,
) -> (
    Option<TokenStream>,
    Punctuated<FnArg, Comma>,
    Cow<'r, ReturnType>,
) {
    log::info!("Beforehand it's {:?}", params.to_token_stream().to_string());
    let has_mutable_receiver = param_details.iter().any(|pd| {
        matches!(pd.self_type, Some((_, ReceiverMutability::Mutable)))
            && !pd.is_placement_return_destination
    });

    let any_param_is_reference = param_details.iter().any(|pd| {
        pd.has_lifetime
            || matches!(
                pd.conversion.rust_conversion,
                RustConversionType::FromValueParamToPtr
            )
    });
    let return_type_is_impl = return_type_is_impl(&ret_type);
    let non_pod_ref_param = reference_parameter_is_non_pod_reference(&params, non_pod_types);
    let ret_type_pod = return_type_is_pod_or_known_type_reference(&ret_type, non_pod_types);
    let returning_impl_with_a_reference_param = return_type_is_impl && any_param_is_reference;
    let hits_1024_bug = non_pod_ref_param && ret_type_pod;
    if !(has_mutable_receiver || hits_1024_bug || returning_impl_with_a_reference_param) {
        return (None, params, ret_type);
    }
    let new_return_type = match ret_type.as_ref() {
        ReturnType::Type(rarrow, boxed_type) => match boxed_type.as_ref() {
            Type::Reference(rtr) => {
                let mut new_rtr = rtr.clone();
                new_rtr.lifetime = Some(parse_quote! { 'a });
                Some(ReturnType::Type(
                    *rarrow,
                    Box::new(Type::Reference(new_rtr)),
                ))
            }
            Type::Path(typ) => {
                let mut new_path = typ.clone();
                add_lifetime_to_pinned_reference(&mut new_path.path.segments)
                    .ok()
                    .map(|_| ReturnType::Type(*rarrow, Box::new(Type::Path(new_path))))
            }
            Type::ImplTrait(tyit) => {
                let old_tyit = tyit.to_token_stream();
                Some(parse_quote! {
                    #rarrow #old_tyit + 'a
                })
            }
            _ => None,
        },
        _ => None,
    };

    match new_return_type {
        None => (None, params, ret_type),
        Some(new_return_type) => {
            for mut param in params.iter_mut() {
                if let FnArg::Typed(PatType { ty, .. }) = &mut param {
                    match ty.as_mut() {
                        Type::Path(TypePath {
                            path: Path { segments, .. },
                            ..
                        }) => add_lifetime_to_pinned_reference(segments).unwrap_or(()),
                        Type::Reference(tyr) => add_lifetime_to_reference(tyr),
                        Type::ImplTrait(tyit) => add_lifetime_to_impl_trait(tyit),
                        _ => {}
                    }
                }
            }

            (Some(quote! { <'a> }), params, Cow::Owned(new_return_type))
        }
    }
}

fn reference_parameter_is_non_pod_reference(
    params: &Punctuated<FnArg, Comma>,
    non_pod_types: &HashSet<QualifiedName>,
) -> bool {
    params.iter().any(|param| match param {
        FnArg::Typed(PatType { ty, .. }) => match ty.as_ref() {
            Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() {
                Type::Path(typ) => {
                    let qn = QualifiedName::from_type_path(typ);
                    non_pod_types.contains(&qn)
                }
                _ => false,
            },
            _ => false,
        },
        _ => false,
    })
}

fn return_type_is_pod_or_known_type_reference(
    ret_type: &ReturnType,
    non_pod_types: &HashSet<QualifiedName>,
) -> bool {
    match ret_type {
        ReturnType::Type(_, boxed_type) => match boxed_type.as_ref() {
            Type::Reference(rtr) => match rtr.elem.as_ref() {
                Type::Path(typ) => {
                    let qn = QualifiedName::from_type_path(typ);
                    !non_pod_types.contains(&qn)
                }
                _ => false,
            },
            _ => false,
        },
        _ => false,
    }
}

fn return_type_is_impl(ret_type: &ReturnType) -> bool {
    matches!(ret_type, ReturnType::Type(_, boxed_type) if matches!(boxed_type.as_ref(), Type::ImplTrait(..)))
}

#[derive(Debug)]
enum AddLifetimeError {
    WasNotPin,
}

fn add_lifetime_to_pinned_reference(
    segments: &mut Punctuated<PathSegment, syn::token::Colon2>,
) -> Result<(), AddLifetimeError> {
    static EXPECTED_SEGMENTS: &[(&str, bool)] = &[
        ("std", false),
        ("pin", false),
        ("Pin", true), // true = act on the arguments of this segment
    ];

    for (seg, (expected_name, act)) in segments.iter_mut().zip(EXPECTED_SEGMENTS.iter()) {
        if seg.ident != expected_name {
            return Err(AddLifetimeError::WasNotPin);
        }
        if *act {
            match &mut seg.arguments {
                syn::PathArguments::AngleBracketed(aba) => match aba.args.iter_mut().next() {
                    Some(GenericArgument::Type(Type::Reference(tyr))) => {
                        add_lifetime_to_reference(tyr);
                    }
                    _ => panic!("Expected generic args with a reference"),
                },
                _ => panic!("Expected angle bracketed args"),
            }
        }
    }
    Ok(())
}

fn add_lifetime_to_reference(tyr: &mut syn::TypeReference) {
    tyr.lifetime = Some(parse_quote! { 'a })
}

fn add_lifetime_to_impl_trait(tyit: &mut syn::TypeImplTrait) {
    tyit.bounds
        .push(syn::TypeParamBound::Lifetime(parse_quote! { 'a }))
}
<|endoftext|>


<commit_before>use super::*;
use crate::btree_overlay::LeafNode;
use crate::merkleize::{merkleize, num_sanitized_leaves, sanitise_bytes};

macro_rules! impl_for_list {
    ($type: ty) => {
        impl<T> CachedTreeHash for $type
        where
            T: CachedTreeHash + TreeHash,
        {
            fn new_tree_hash_cache(&self, depth: usize) -> Result<TreeHashCache, Error> {
                let (mut cache, schema) = new_tree_hash_cache(self, depth)?;

                cache.add_length_nodes(schema.into_overlay(0).chunk_range(), self.len())?;

                Ok(cache)
            }

            fn num_tree_hash_cache_chunks(&self) -> usize {
                // Add two extra nodes to cater for the node before and after to allow mixing-in length.
                BTreeOverlay::new(self, 0, 0).num_chunks() + 2
            }

            fn tree_hash_cache_schema(&self, depth: usize) -> BTreeSchema {
                produce_schema(self, depth)
            }

            fn update_tree_hash_cache(&self, cache: &mut TreeHashCache) -> Result<(), Error> {
                // Skip the length-mixed-in root node.
                cache.chunk_index += 1;

                // Update the cache, returning the new overlay.
                let new_overlay = update_tree_hash_cache(&self, cache)?;

                // Mix in length
                cache.mix_in_length(new_overlay.chunk_range(), self.len())?;

                // Skip an extra node to clear the length node.
                cache.chunk_index += 1;

                Ok(())
            }
        }
    };
}

impl_for_list!(Vec<T>);
impl_for_list!(&[T]);

pub fn new_tree_hash_cache<T: CachedTreeHash>(
    vec: &[T],
    depth: usize,
) -> Result<(TreeHashCache, BTreeSchema), Error> {
    let schema = vec.tree_hash_cache_schema(depth);

    let cache = match T::tree_hash_type() {
        TreeHashType::Basic => TreeHashCache::from_bytes(
            merkleize(get_packed_leaves(vec)?),
            false,
            Some(schema.clone()),
        ),
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let subtrees = vec
                .iter()
                .map(|item| TreeHashCache::new(item, depth + 1))
                .collect::<Result<Vec<TreeHashCache>, _>>()?;

            TreeHashCache::from_leaves_and_subtrees(&vec, subtrees, depth)
        }
    }?;

    Ok((cache, schema))
}

pub fn produce_schema<T: CachedTreeHash>(vec: &[T], depth: usize) -> BTreeSchema {
    let lengths = match T::tree_hash_type() {
        TreeHashType::Basic => {
            // Ceil division.
            let num_leaves =
                (vec.len() + T::tree_hash_packing_factor() - 1) / T::tree_hash_packing_factor();

            // Disallow zero-length as an empty list still has one all-padding node.
            vec![1; std::cmp::max(1, num_leaves)]
        }
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let mut lengths = vec![];

            for item in vec {
                lengths.push(item.num_tree_hash_cache_chunks())
            }

            lengths
        }
    };

    BTreeSchema::from_lengths(depth, lengths)
}

#[allow(clippy::range_plus_one)] // Minor readability lint requiring structural changes; not worth it.
pub fn update_tree_hash_cache<T: CachedTreeHash>(
    vec: &[T],
    cache: &mut TreeHashCache,
) -> Result<BTreeOverlay, Error> {
    let old_overlay = cache.get_overlay(cache.schema_index, cache.chunk_index)?;
    let new_overlay = BTreeOverlay::new(&vec, cache.chunk_index, old_overlay.depth);

    cache.replace_overlay(cache.schema_index, cache.chunk_index, new_overlay.clone())?;

    cache.schema_index += 1;

    match T::tree_hash_type() {
        TreeHashType::Basic => {
            let mut buf = vec![0; HASHSIZE];
            let item_bytes = HASHSIZE / T::tree_hash_packing_factor();

            // If the number of leaf nodes has changed, resize the cache.
            if new_overlay.num_leaf_nodes() < old_overlay.num_leaf_nodes() {
                let start = new_overlay.next_node();
                let end = start + (old_overlay.num_leaf_nodes() - new_overlay.num_leaf_nodes());

                cache.splice(start..end, vec![], vec![]);
            } else if new_overlay.num_leaf_nodes() > old_overlay.num_leaf_nodes() {
                let start = old_overlay.next_node();
                let new_nodes = new_overlay.num_leaf_nodes() - old_overlay.num_leaf_nodes();

                cache.splice(
                    start..start,
                    vec![0; new_nodes * HASHSIZE],
                    vec![true; new_nodes],
                );
            }

            // Iterate through each of the leaf nodes in the new list.
            for i in 0..new_overlay.num_leaf_nodes() {
                // Iterate through the number of items that may be packing into the leaf node.
                for j in 0..T::tree_hash_packing_factor() {
                    // Create a mut slice that can be filled with either a serialized item or
                    // padding.
                    let buf_slice = &mut buf[j * item_bytes..(j + 1) * item_bytes];

                    // Attempt to get the item for this portion of the chunk. If it exists,
                    // update `buf` with it's serialized bytes. If it doesn't exist, update
                    // `buf` with padding.
                    match vec.get(i * T::tree_hash_packing_factor() + j) {
                        Some(item) => {
                            buf_slice.copy_from_slice(&item.tree_hash_packed_encoding());
                        }
                        None => buf_slice.copy_from_slice(&vec![0; item_bytes]),
                    }
                }

                // Update the chunk if the generated `buf` is not the same as the cache.
                let chunk = new_overlay.first_leaf_node() + i;
                cache.maybe_update_chunk(chunk, &buf)?;
            }
        }
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let longest_len =
                std::cmp::max(new_overlay.num_leaf_nodes(), old_overlay.num_leaf_nodes());

            let old_leaf_nodes = old_overlay.get_leaf_nodes(longest_len);
            let new_leaf_nodes = if old_overlay == new_overlay {
                old_leaf_nodes.clone()
            } else {
                new_overlay.get_leaf_nodes(longest_len)
            };

            for i in 0..longest_len {
                match (&old_leaf_nodes[i], &new_leaf_nodes[i]) {
                    // The item existed in the previous list and exists in the current list.
                    //
                    // Update the item.
                    (LeafNode::Exists(_old), LeafNode::Exists(new)) => {
                        cache.chunk_index = new.start;

                        vec[i].update_tree_hash_cache(cache)?;
                    }
                    // The list has been lengthened and this is a new item that did not exist in
                    // the previous list.
                    //
                    // Splice the tree for the new item into the current chunk_index.
                    (LeafNode::DoesNotExist, LeafNode::Exists(new)) => {
                        splice_in_new_tree(
                            &vec[i],
                            new.start..new.start,
                            new_overlay.depth + 1,
                            cache,
                        )?;

                        cache.chunk_index = new.end;
                    }
                    // The list has been lengthened and this is a new item that was prevously a
                    // padding item.
                    //
                    // Splice the tree for the new item over the padding chunk.
                    (LeafNode::Padding, LeafNode::Exists(new)) => {
                        splice_in_new_tree(
                            &vec[i],
                            new.start..new.start + 1,
                            new_overlay.depth + 1,
                            cache,
                        )?;

                        cache.chunk_index = new.end;
                    }
                    // The list has been shortened and this item was removed from the list and made
                    // into padding.
                    //
                    // Splice a padding node over the number of nodes the previous item occupied,
                    // starting at the current chunk_index.
                    (LeafNode::Exists(old), LeafNode::Padding) => {
                        let num_chunks = old.end - old.start;

                        cache.splice(
                            cache.chunk_index..cache.chunk_index + num_chunks,
                            vec![0; HASHSIZE],
                            vec![true],
                        );

                        cache.chunk_index += 1;
                    }
                    // The list has been shortened and the item for this leaf existed in the
                    // previous list, but does not exist in this list.
                    //
                    // Remove the number of nodes the previous item occupied, starting at the
                    // current chunk_index.
                    (LeafNode::Exists(old), LeafNode::DoesNotExist) => {
                        let num_chunks = old.end - old.start;

                        cache.splice(
                            cache.chunk_index..cache.chunk_index + num_chunks,
                            vec![],
                            vec![],
                        );
                    }
                    // The list has been shortened and this leaf was padding in the previous list,
                    // however it should not exist in this list.
                    //
                    // Remove one node, starting at the current `chunk_index`.
                    (LeafNode::Padding, LeafNode::DoesNotExist) => {
                        cache.splice(cache.chunk_index..cache.chunk_index + 1, vec![], vec![]);
                    }
                    // The list has been lengthened and this leaf did not exist in the previous
                    // list, but should be padding for this list.
                    //
                    // Splice in a new padding node at the current chunk_index.
                    (LeafNode::DoesNotExist, LeafNode::Padding) => {
                        cache.splice(
                            cache.chunk_index..cache.chunk_index,
                            vec![0; HASHSIZE],
                            vec![true],
                        );

                        cache.chunk_index += 1;
                    }
                    // This leaf was padding in both lists, there's nothing to do.
                    (LeafNode::Padding, LeafNode::Padding) => (),
                    // As we are looping through the larger of the lists of leaf nodes, it should
                    // be impossible for either leaf to be non-existant.
                    (LeafNode::DoesNotExist, LeafNode::DoesNotExist) => unreachable!(),
                }
            }

            // Clean out any excess schemas that may or may not be remaining if the list was
            // shortened.
            cache.remove_proceeding_child_schemas(cache.schema_index, new_overlay.depth);
        }
    }

    cache.update_internal_nodes(&new_overlay)?;

    cache.chunk_index = new_overlay.next_node();

    Ok(new_overlay)
}

fn splice_in_new_tree<T>(
    item: &T,
    chunks_to_replace: Range<usize>,
    depth: usize,
    cache: &mut TreeHashCache,
) -> Result<(), Error>
where
    T: CachedTreeHash,
{
    let (bytes, mut bools, schemas) = TreeHashCache::new(item, depth)?.into_components();

    // Record the number of schemas, this will be used later in the fn.
    let num_schemas = schemas.len();

    // Flag the root node of the new tree as dirty.
    bools[0] = true;

    cache.splice(chunks_to_replace, bytes, bools);
    cache
        .schemas
        .splice(cache.schema_index..cache.schema_index, schemas);

    cache.schema_index += num_schemas;

    Ok(())
    //
}

fn get_packed_leaves<T>(vec: &[T]) -> Result<Vec<u8>, Error>
where
    T: CachedTreeHash,
{
    let num_packed_bytes = (BYTES_PER_CHUNK / T::tree_hash_packing_factor()) * vec.len();
    let num_leaves = num_sanitized_leaves(num_packed_bytes);

    let mut packed = Vec::with_capacity(num_leaves * HASHSIZE);

    for item in vec {
        packed.append(&mut item.tree_hash_packed_encoding());
    }

    Ok(sanitise_bytes(packed))
}
<commit_msg>Add doc comments to vec impl<commit_after>use super::*;
use crate::btree_overlay::LeafNode;
use crate::merkleize::{merkleize, num_sanitized_leaves, sanitise_bytes};

macro_rules! impl_for_list {
    ($type: ty) => {
        impl<T> CachedTreeHash for $type
        where
            T: CachedTreeHash + TreeHash,
        {
            fn new_tree_hash_cache(&self, depth: usize) -> Result<TreeHashCache, Error> {
                let (mut cache, schema) = new_tree_hash_cache(self, depth)?;

                cache.add_length_nodes(schema.into_overlay(0).chunk_range(), self.len())?;

                Ok(cache)
            }

            fn num_tree_hash_cache_chunks(&self) -> usize {
                // Add two extra nodes to cater for the node before and after to allow mixing-in length.
                BTreeOverlay::new(self, 0, 0).num_chunks() + 2
            }

            fn tree_hash_cache_schema(&self, depth: usize) -> BTreeSchema {
                produce_schema(self, depth)
            }

            fn update_tree_hash_cache(&self, cache: &mut TreeHashCache) -> Result<(), Error> {
                // Skip the length-mixed-in root node.
                cache.chunk_index += 1;

                // Update the cache, returning the new overlay.
                let new_overlay = update_tree_hash_cache(&self, cache)?;

                // Mix in length
                cache.mix_in_length(new_overlay.chunk_range(), self.len())?;

                // Skip an extra node to clear the length node.
                cache.chunk_index += 1;

                Ok(())
            }
        }
    };
}

impl_for_list!(Vec<T>);
impl_for_list!(&[T]);

/// Build a new tree hash cache for some slice.
///
/// Valid for both variable- and fixed-length slices. Does _not_ mix-in the length of the list,
/// the caller must do this.
pub fn new_tree_hash_cache<T: CachedTreeHash>(
    vec: &[T],
    depth: usize,
) -> Result<(TreeHashCache, BTreeSchema), Error> {
    let schema = vec.tree_hash_cache_schema(depth);

    let cache = match T::tree_hash_type() {
        TreeHashType::Basic => TreeHashCache::from_bytes(
            merkleize(get_packed_leaves(vec)?),
            false,
            Some(schema.clone()),
        ),
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let subtrees = vec
                .iter()
                .map(|item| TreeHashCache::new(item, depth + 1))
                .collect::<Result<Vec<TreeHashCache>, _>>()?;

            TreeHashCache::from_leaves_and_subtrees(&vec, subtrees, depth)
        }
    }?;

    Ok((cache, schema))
}

/// Produce a schema for some slice.
///
/// Valid for both variable- and fixed-length slices. Does _not_ add the mix-in length nodes, the
/// caller must do this.
pub fn produce_schema<T: CachedTreeHash>(vec: &[T], depth: usize) -> BTreeSchema {
    let lengths = match T::tree_hash_type() {
        TreeHashType::Basic => {
            // Ceil division.
            let num_leaves =
                (vec.len() + T::tree_hash_packing_factor() - 1) / T::tree_hash_packing_factor();

            // Disallow zero-length as an empty list still has one all-padding node.
            vec![1; std::cmp::max(1, num_leaves)]
        }
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let mut lengths = vec![];

            for item in vec {
                lengths.push(item.num_tree_hash_cache_chunks())
            }

            lengths
        }
    };

    BTreeSchema::from_lengths(depth, lengths)
}

/// Updates the cache for some slice.
///
/// Valid for both variable- and fixed-length slices. Does _not_ cater for the mix-in length nodes,
/// the caller must do this.
#[allow(clippy::range_plus_one)] // Minor readability lint requiring structural changes; not worth it.
pub fn update_tree_hash_cache<T: CachedTreeHash>(
    vec: &[T],
    cache: &mut TreeHashCache,
) -> Result<BTreeOverlay, Error> {
    let old_overlay = cache.get_overlay(cache.schema_index, cache.chunk_index)?;
    let new_overlay = BTreeOverlay::new(&vec, cache.chunk_index, old_overlay.depth);

    cache.replace_overlay(cache.schema_index, cache.chunk_index, new_overlay.clone())?;

    cache.schema_index += 1;

    match T::tree_hash_type() {
        TreeHashType::Basic => {
            let mut buf = vec![0; HASHSIZE];
            let item_bytes = HASHSIZE / T::tree_hash_packing_factor();

            // If the number of leaf nodes has changed, resize the cache.
            if new_overlay.num_leaf_nodes() < old_overlay.num_leaf_nodes() {
                let start = new_overlay.next_node();
                let end = start + (old_overlay.num_leaf_nodes() - new_overlay.num_leaf_nodes());

                cache.splice(start..end, vec![], vec![]);
            } else if new_overlay.num_leaf_nodes() > old_overlay.num_leaf_nodes() {
                let start = old_overlay.next_node();
                let new_nodes = new_overlay.num_leaf_nodes() - old_overlay.num_leaf_nodes();

                cache.splice(
                    start..start,
                    vec![0; new_nodes * HASHSIZE],
                    vec![true; new_nodes],
                );
            }

            // Iterate through each of the leaf nodes in the new list.
            for i in 0..new_overlay.num_leaf_nodes() {
                // Iterate through the number of items that may be packing into the leaf node.
                for j in 0..T::tree_hash_packing_factor() {
                    // Create a mut slice that can be filled with either a serialized item or
                    // padding.
                    let buf_slice = &mut buf[j * item_bytes..(j + 1) * item_bytes];

                    // Attempt to get the item for this portion of the chunk. If it exists,
                    // update `buf` with it's serialized bytes. If it doesn't exist, update
                    // `buf` with padding.
                    match vec.get(i * T::tree_hash_packing_factor() + j) {
                        Some(item) => {
                            buf_slice.copy_from_slice(&item.tree_hash_packed_encoding());
                        }
                        None => buf_slice.copy_from_slice(&vec![0; item_bytes]),
                    }
                }

                // Update the chunk if the generated `buf` is not the same as the cache.
                let chunk = new_overlay.first_leaf_node() + i;
                cache.maybe_update_chunk(chunk, &buf)?;
            }
        }
        TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
            let longest_len =
                std::cmp::max(new_overlay.num_leaf_nodes(), old_overlay.num_leaf_nodes());

            let old_leaf_nodes = old_overlay.get_leaf_nodes(longest_len);
            let new_leaf_nodes = if old_overlay == new_overlay {
                old_leaf_nodes.clone()
            } else {
                new_overlay.get_leaf_nodes(longest_len)
            };

            for i in 0..longest_len {
                match (&old_leaf_nodes[i], &new_leaf_nodes[i]) {
                    // The item existed in the previous list and exists in the current list.
                    //
                    // Update the item.
                    (LeafNode::Exists(_old), LeafNode::Exists(new)) => {
                        cache.chunk_index = new.start;

                        vec[i].update_tree_hash_cache(cache)?;
                    }
                    // The list has been lengthened and this is a new item that did not exist in
                    // the previous list.
                    //
                    // Splice the tree for the new item into the current chunk_index.
                    (LeafNode::DoesNotExist, LeafNode::Exists(new)) => {
                        splice_in_new_tree(
                            &vec[i],
                            new.start..new.start,
                            new_overlay.depth + 1,
                            cache,
                        )?;

                        cache.chunk_index = new.end;
                    }
                    // The list has been lengthened and this is a new item that was prevously a
                    // padding item.
                    //
                    // Splice the tree for the new item over the padding chunk.
                    (LeafNode::Padding, LeafNode::Exists(new)) => {
                        splice_in_new_tree(
                            &vec[i],
                            new.start..new.start + 1,
                            new_overlay.depth + 1,
                            cache,
                        )?;

                        cache.chunk_index = new.end;
                    }
                    // The list has been shortened and this item was removed from the list and made
                    // into padding.
                    //
                    // Splice a padding node over the number of nodes the previous item occupied,
                    // starting at the current chunk_index.
                    (LeafNode::Exists(old), LeafNode::Padding) => {
                        let num_chunks = old.end - old.start;

                        cache.splice(
                            cache.chunk_index..cache.chunk_index + num_chunks,
                            vec![0; HASHSIZE],
                            vec![true],
                        );

                        cache.chunk_index += 1;
                    }
                    // The list has been shortened and the item for this leaf existed in the
                    // previous list, but does not exist in this list.
                    //
                    // Remove the number of nodes the previous item occupied, starting at the
                    // current chunk_index.
                    (LeafNode::Exists(old), LeafNode::DoesNotExist) => {
                        let num_chunks = old.end - old.start;

                        cache.splice(
                            cache.chunk_index..cache.chunk_index + num_chunks,
                            vec![],
                            vec![],
                        );
                    }
                    // The list has been shortened and this leaf was padding in the previous list,
                    // however it should not exist in this list.
                    //
                    // Remove one node, starting at the current `chunk_index`.
                    (LeafNode::Padding, LeafNode::DoesNotExist) => {
                        cache.splice(cache.chunk_index..cache.chunk_index + 1, vec![], vec![]);
                    }
                    // The list has been lengthened and this leaf did not exist in the previous
                    // list, but should be padding for this list.
                    //
                    // Splice in a new padding node at the current chunk_index.
                    (LeafNode::DoesNotExist, LeafNode::Padding) => {
                        cache.splice(
                            cache.chunk_index..cache.chunk_index,
                            vec![0; HASHSIZE],
                            vec![true],
                        );

                        cache.chunk_index += 1;
                    }
                    // This leaf was padding in both lists, there's nothing to do.
                    (LeafNode::Padding, LeafNode::Padding) => (),
                    // As we are looping through the larger of the lists of leaf nodes, it should
                    // be impossible for either leaf to be non-existant.
                    (LeafNode::DoesNotExist, LeafNode::DoesNotExist) => unreachable!(),
                }
            }

            // Clean out any excess schemas that may or may not be remaining if the list was
            // shortened.
            cache.remove_proceeding_child_schemas(cache.schema_index, new_overlay.depth);
        }
    }

    cache.update_internal_nodes(&new_overlay)?;

    cache.chunk_index = new_overlay.next_node();

    Ok(new_overlay)
}

/// Create a new `TreeHashCache` from `item` and splice it over the `chunks_to_replace` chunks of
/// the given `cache`.
///
/// Useful for the case where a new element is added to a list.
///
/// The schemas created for `item` will have the given `depth`.
fn splice_in_new_tree<T>(
    item: &T,
    chunks_to_replace: Range<usize>,
    depth: usize,
    cache: &mut TreeHashCache,
) -> Result<(), Error>
where
    T: CachedTreeHash,
{
    let (bytes, mut bools, schemas) = TreeHashCache::new(item, depth)?.into_components();

    // Record the number of schemas, this will be used later in the fn.
    let num_schemas = schemas.len();

    // Flag the root node of the new tree as dirty.
    bools[0] = true;

    cache.splice(chunks_to_replace, bytes, bools);
    cache
        .schemas
        .splice(cache.schema_index..cache.schema_index, schemas);

    cache.schema_index += num_schemas;

    Ok(())
}

/// Packs all of the leaves of `vec` into a single byte-array, appending `0` to ensure the number
/// of chunks in the byte-array is a power-of-two.
fn get_packed_leaves<T>(vec: &[T]) -> Result<Vec<u8>, Error>
where
    T: CachedTreeHash,
{
    let num_packed_bytes = (BYTES_PER_CHUNK / T::tree_hash_packing_factor()) * vec.len();
    let num_leaves = num_sanitized_leaves(num_packed_bytes);

    let mut packed = Vec::with_capacity(num_leaves * HASHSIZE);

    for item in vec {
        packed.append(&mut item.tree_hash_packed_encoding());
    }

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


<commit_before>use std::num::Bounded;
use std::io::{File, FileMode, Open, Read, Write, IoResult, SeekSet, OtherIoError, IoError};

static UNDEF_RECORD:u32 = 0xFEFEFEFF;
static INDEX_SIZE: uint = 12;

pub struct MulRecord {
    pub data: Vec<u8>,
    pub start: u32,
    pub length: u32,
    pub opt1: u16,
    pub opt2: u16
}

pub struct MulReader {
    idx_reader: File,
    data_reader: File
}

impl MulReader {

    pub fn new(idx_path: &Path, mul_path: &Path) -> IoResult<MulReader> {
        let idx = File::open_mode(idx_path, Open, Read);
        let mul = File::open_mode(mul_path, Open, Read);

        match (idx, mul) {
            (Ok(idx_reader), Ok(data_reader)) => Ok(MulReader {
                idx_reader: idx_reader,
                data_reader: data_reader
            }),
            (Err(err), _) => Err(err),
            (_, Err(err)) => Err(err)
        }
    }

    pub fn read(&mut self, index: uint) -> IoResult<MulRecord> {
        //Wind the idx reader to the index position
        let data = self.idx_reader.seek((index * INDEX_SIZE) as i64, SeekSet).and_then(|()| {
            self.idx_reader.read_le_uint_n(4).and_then(|start| {
                self.idx_reader.read_le_uint_n(4).and_then(|length| {
                    self.idx_reader.read_le_uint_n(2).and_then(|opt1| {
                        self.idx_reader.read_le_uint_n(2).and_then(|opt2| {
                            Ok((start as u32, length as u32, opt1 as u16, opt2 as u16))
                        })
                    })
                })
            })
        });

        match data {
            Ok((start, length, opt1, opt2)) => {
                //Check for empty cell
                if start == UNDEF_RECORD || start == Bounded::max_value() {
                    Err(IoError {
                        kind: OtherIoError,
                        desc: "Trying to read out-of-bounds record",
                        detail: Some(format!("Trying to read out of bounds record {}, with a start of {}", index, start))
                    })
                } else {
                    let maybe_data = self.data_reader.seek(start as i64, SeekSet).and_then(|()| {
                        self.data_reader.read_exact(length as uint)
                    });
                    match maybe_data {
                        Ok(data) => {
                            Ok(MulRecord {
                                data: data,
                                start: start,
                                length: length,
                                opt1: opt1,
                                opt2: opt2
                            })
                        },
                        Err(err) => Err(err)
                    }
                }
            },
            Err(err) => Err(err)
        }
    }
}

pub struct MulWriter {
    idx_writer: File,
    data_writer: File
}

impl MulWriter{

    pub fn new(idx_path: &Path, mul_path: &Path, method: FileMode) -> IoResult<MulWriter> {
        let idx = File::open_mode(idx_path, method, Write);
        let mul = File::open_mode(mul_path, method, Write);

        match (idx, mul) {
            (Ok(idx_writer), Ok(data_writer)) => Ok(MulWriter {
                idx_writer: idx_writer,
                data_writer: data_writer
            }),
            (Err(err), _) => Err(err),
            (_, Err(err)) => Err(err)
        }
    }

    pub fn append(&mut self, data: &Vec<u8>, opt1: Option<u16>, opt2: Option<u16>) -> IoResult<()> {

        let sizes = self.idx_writer.stat().and_then(|idx_stat| {
            self.data_writer.stat().and_then(|data_stat| {
                Ok((idx_stat.size as i64, data_stat.size as i64))
            })
        });

        if sizes.is_err() {
            return Err(sizes.unwrap_err());
        };

        let (idx_size, mul_size) = match sizes {
            Ok(result) => result,
            Err(_) => fail!("Should be impossible")
        };

        //Wind the files to the end
        let seek_result = self.idx_writer.seek(idx_size, SeekSet).and_then(|()| {
            self.data_writer.seek(mul_size, SeekSet)
        });

        match seek_result {
            Ok(_) => {
                //Fill up our fields
                let start = mul_size as u32;
                let length = data.len() as u32;
                let opt1 = match opt1 { Some(value) => value, None => 0} as u16;
                let opt2 = match opt2 { Some(value) => value, None => 0} as u16;
                self.data_writer.write(data.as_slice()).and_then(|()| {
                    self.idx_writer.write_le_u32(start).and_then(|()| {
                        self.idx_writer.write_le_u32(length).and_then(|()| {
                            self.idx_writer.write_le_u16(opt1).and_then(|()| {
                                self.idx_writer.write_le_u16(opt2)
                            })
                        })
                    })
                })
            },
            Err(err) => Err(err)
        }
    }
}
<commit_msg>Modernised mul_reader<commit_after>use std::num::Bounded;
use std::io::{File, FileMode, Open, Read, Write, IoResult, SeekSet, OtherIoError, IoError};

static UNDEF_RECORD: u32 = 0xFEFEFEFF;
static INDEX_SIZE: u32 = 12;

pub struct MulRecord {
    pub data: Vec<u8>,
    pub start: u32,
    pub length: u32,
    pub opt1: u16,
    pub opt2: u16
}

pub struct MulReader {
    idx_reader: File,
    data_reader: File
}

impl MulReader {

    pub fn new(idx_path: &Path, mul_path: &Path) -> IoResult<MulReader> {
        let idx_reader = try!(File::open_mode(idx_path, Open, Read));
        let data_reader = try!(File::open_mode(mul_path, Open, Read));

        Ok(MulReader {
            idx_reader: idx_reader,
            data_reader: data_reader
        })
    }

    pub fn read(&mut self, index: u32) -> IoResult<MulRecord> {
        //Wind the idx reader to the index position
        try!(self.idx_reader.seek((index * INDEX_SIZE) as i64, SeekSet));
        let start = try!(self.idx_reader.read_le_u32());
        if start == UNDEF_RECORD || start == Bounded::max_value() {
            //Check for empty cell
            Err(IoError {
                kind: OtherIoError,
                desc: "Trying to read out-of-bounds record",
                detail: Some(format!("Trying to read out of bounds record {}, with a start of {}", index, start))
            })
        } else {
            let length = try!(self.idx_reader.read_le_u32());
            let opt1 = try!(self.idx_reader.read_le_u16());
            let opt2 = try!(self.idx_reader.read_le_u16());
            try!(self.data_reader.seek(start as i64, SeekSet));

            let data = try!(self.data_reader.read_exact(length as uint));

            Ok(MulRecord {
                data: data,
                start: start,
                length: length,
                opt1: opt1,
                opt2: opt2
            })
        }
    }
}

pub struct MulWriter {
    idx_writer: File,
    data_writer: File
}

impl MulWriter {

    pub fn new(idx_path: &Path, mul_path: &Path, method: FileMode) -> IoResult<MulWriter> {
        let idx_writer = try!(File::open_mode(idx_path, method, Write));
        let data_writer = try!(File::open_mode(mul_path, method, Write));

        Ok(MulWriter {
            idx_writer: idx_writer,
            data_writer: data_writer
        })
    }

    pub fn append(&mut self, data: &Vec<u8>, opt1: Option<u16>, opt2: Option<u16>) -> IoResult<()> {

        let idx_size = try!(self.idx_writer.stat()).size as i64;
        let mul_size = try!(self.data_writer.stat()).size as i64;

        //Wind the files to the end
        try!(self.idx_writer.seek(idx_size, SeekSet));
        try!(self.data_writer.seek(mul_size, SeekSet));

        //Fill up our fields
        let start = mul_size as u32;
        let length = data.len() as u32;
        let opt1 = match opt1 { Some(value) => value, None => 0} as u16;
        let opt2 = match opt2 { Some(value) => value, None => 0} as u16;

        try!(self.data_writer.write(data.as_slice()));
        try!(self.idx_writer.write_le_u32(start));
        try!(self.idx_writer.write_le_u32(length));
        try!(self.idx_writer.write_le_u16(opt1));
        try!(self.idx_writer.write_le_u16(opt2));

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


<commit_before>use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as FWrite;
use std::io::{Read, Write};
use std::default::Default;
use semver::VersionReq;
use std::borrow::Cow;
use std::path::Path;
use std::fs::File;
use toml;

/// A single operation to be executed upon configuration of a package.
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ConfigOperation {
    /// Set the toolchain to use to compile the package.
    SetToolchain(String),
    /// Use the default toolchain to use to compile the package.
    RemoveToolchain,
    /// Whether to compile the package with the default features.
    DefaultFeatures(bool),
    /// Compile the package with the specified feature.
    AddFeature(String),
    /// Remove the feature from the list of features to compile with.
    RemoveFeature(String),
    /// Set debug mode being enabled to the specified value.
    SetDebugMode(bool),
    /// Set allowing to install prereleases to the specified value.
    SetInstallPrereleases(bool),
    /// Set enforcing Cargo.lock to the specified value.
    SetEnforceLock(bool),
    /// Set installing only the pre-set binaries.
    SetRespectBinaries(bool),
    /// Constrain the installed to the specified one.
    SetTargetVersion(VersionReq),
    /// Always install latest package version.
    RemoveTargetVersion,
}

/// Compilation configuration for one crate.
///
/// # Examples
///
/// Reading a configset, adding an entry to it, then writing it back.
///
/// ```
/// # use cargo_update::ops::PackageConfig;
/// # use std::fs::{File, create_dir_all};
/// # use std::env::temp_dir;
/// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-0");
/// # create_dir_all(&td).unwrap();
/// # let config_file = td.join(".install_config.toml");
/// # let operations = [];
/// let mut configuration = PackageConfig::read(&config_file).unwrap();
/// configuration.insert("cargo_update".to_string(), PackageConfig::from(&operations));
/// PackageConfig::write(&configuration, &config_file).unwrap();
/// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PackageConfig {
    /// Toolchain to use to compile the package, or `None` for default.
    pub toolchain: Option<String>,
    /// Whether to compile the package with the default features.
    pub default_features: bool,
    /// Features to compile the package with.
    pub features: BTreeSet<String>,
    /// Whether to compile in debug mode.
    pub debug: Option<bool>,
    /// Whether to install pre-release versions.
    pub install_prereleases: Option<bool>,
    /// Whether to enforce Cargo.lock versions.
    pub enforce_lock: Option<bool>,
    /// Whether to install only the pre-configured binaries.
    pub respect_binaries: Option<bool>,
    /// Versions to constrain to.
    pub target_version: Option<VersionReq>,
}

impl PackageConfig {
    /// Create a package config based on the default settings and modified according to the specified operations.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_update;
    /// # extern crate semver;
    /// # fn main() {
    /// # use cargo_update::ops::{ConfigOperation, PackageConfig};
    /// # use std::collections::BTreeSet;
    /// # use semver::VersionReq;
    /// # use std::str::FromStr;
    /// assert_eq!(PackageConfig::from(&[ConfigOperation::SetToolchain("nightly".to_string()),
    ///                                  ConfigOperation::DefaultFeatures(false),
    ///                                  ConfigOperation::AddFeature("rustc-serialize".to_string()),
    ///                                  ConfigOperation::SetDebugMode(true),
    ///                                  ConfigOperation::SetInstallPrereleases(false),
    ///                                  ConfigOperation::SetEnforceLock(true),
    ///                                  ConfigOperation::SetRespectBinaries(true),
    ///                                  ConfigOperation::SetTargetVersion(VersionReq::from_str(">=0.1").unwrap())]),
    ///            PackageConfig {
    ///                toolchain: Some("nightly".to_string()),
    ///                default_features: false,
    ///                features: {
    ///                    let mut feats = BTreeSet::new();
    ///                    feats.insert("rustc-serialize".to_string());
    ///                    feats
    ///                },
    ///                debug: Some(true),
    ///                install_prereleases: Some(false),
    ///                enforce_lock: Some(true),
    ///                respect_binaries: Some(true),
    ///                target_version: Some(VersionReq::from_str(">=0.1").unwrap()),
    ///            });
    /// # }
    /// ```
    pub fn from<'o, O: IntoIterator<Item = &'o ConfigOperation>>(ops: O) -> PackageConfig {
        let mut def = PackageConfig::default();
        def.execute_operations(ops);
        def
    }

    /// Generate cargo arguments from this configuration.
    ///
    /// Executable names are stripped of their trailing `".exe"`, if any.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::collections::BTreeMap;
    /// # use std::process::Command;
    /// # let name = "cargo-update".to_string();
    /// # let mut configuration = BTreeMap::new();
    /// # configuration.insert(name.clone(), PackageConfig::from(&[]));
    /// let cmd = Command::new("cargo").args(configuration.get(&name).unwrap().cargo_args(&["racer"]).iter().map(AsRef::as_ref)).arg(&name)
    /// // Process the command further -- run it, for example.
    /// # .status().unwrap();
    /// # let _ = cmd;
    /// ```
    pub fn cargo_args<S: AsRef<str>, I: IntoIterator<Item = S>>(&self, executables: I) -> Vec<Cow<'static, str>> {
        let mut res = vec![];
        if let Some(ref t) = self.toolchain {
            res.push(format!("+{}", t).into());
        }
        res.push("install".into());
        res.push("-f".into());
        if !self.default_features {
            res.push("--no-default-features".into());
        }
        if !self.features.is_empty() {
            res.push("--features".into());
            let mut a = String::new();
            for f in &self.features {
                write!(a, "{} ", f).unwrap();
            }
            res.push(a.into());
        }
        if let Some(true) = self.enforce_lock {
            res.push("--locked".into());
        }
        if let Some(true) = self.respect_binaries {
            for x in executables {
                let x = x.as_ref();

                res.push("--bin".into());
                res.push(x.strip_suffix(".exe").unwrap_or(x).to_string().into());
            }
        }
        if let Some(true) = self.debug {
            res.push("--debug".into());
        }
        res
    }

    /// Modify `self` according to the specified set of operations.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_update;
    /// # extern crate semver;
    /// # fn main() {
    /// # use cargo_update::ops::{ConfigOperation, PackageConfig};
    /// # use std::collections::BTreeSet;
    /// # use semver::VersionReq;
    /// # use std::str::FromStr;
    /// let mut cfg = PackageConfig {
    ///     toolchain: Some("nightly".to_string()),
    ///     default_features: false,
    ///     features: {
    ///         let mut feats = BTreeSet::new();
    ///         feats.insert("rustc-serialize".to_string());
    ///         feats
    ///     },
    ///     debug: None,
    ///     install_prereleases: None,
    ///     enforce_lock: None,
    ///     respect_binaries: None,
    ///     target_version: Some(VersionReq::from_str(">=0.1").unwrap()),
    /// };
    /// cfg.execute_operations(&[ConfigOperation::RemoveToolchain,
    ///                          ConfigOperation::AddFeature("serde".to_string()),
    ///                          ConfigOperation::RemoveFeature("rustc-serialize".to_string()),
    ///                          ConfigOperation::SetDebugMode(true),
    ///                          ConfigOperation::RemoveTargetVersion]);
    /// assert_eq!(cfg,
    ///            PackageConfig {
    ///                toolchain: None,
    ///                default_features: false,
    ///                features: {
    ///                    let mut feats = BTreeSet::new();
    ///                    feats.insert("serde".to_string());
    ///                    feats
    ///                },
    ///                debug: Some(true),
    ///                install_prereleases: None,
    ///                enforce_lock: None,
    ///                respect_binaries: None,
    ///                target_version: None,
    ///            });
    /// # }
    /// ```
    pub fn execute_operations<'o, O: IntoIterator<Item = &'o ConfigOperation>>(&mut self, ops: O) {
        for op in ops {
            self.execute_operation(op)
        }
    }

    fn execute_operation(&mut self, op: &ConfigOperation) {
        match *op {
            ConfigOperation::SetToolchain(ref tchn) => self.toolchain = Some(tchn.clone()),
            ConfigOperation::RemoveToolchain => self.toolchain = None,
            ConfigOperation::DefaultFeatures(f) => self.default_features = f,
            ConfigOperation::AddFeature(ref feat) => {
                self.features.insert(feat.clone());
            }
            ConfigOperation::RemoveFeature(ref feat) => {
                self.features.remove(feat);
            }
            ConfigOperation::SetDebugMode(d) => self.debug = Some(d),
            ConfigOperation::SetInstallPrereleases(pr) => self.install_prereleases = Some(pr),
            ConfigOperation::SetEnforceLock(el) => self.enforce_lock = Some(el),
            ConfigOperation::SetRespectBinaries(rb) => self.respect_binaries = Some(rb),
            ConfigOperation::SetTargetVersion(ref vr) => self.target_version = Some(vr.clone()),
            ConfigOperation::RemoveTargetVersion => self.target_version = None,
        }
    }

    /// Read a configset from the specified file.
    ///
    /// If the specified file doesn't exist an empty configset is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::collections::{BTreeSet, BTreeMap};
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::fs::{File, create_dir_all};
    /// # use std::env::temp_dir;
    /// # use std::io::Write;
    /// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-read-0");
    /// # create_dir_all(&td).unwrap();
    /// # let config_file = td.join(".install_config.toml");
    /// File::create(&config_file).unwrap().write_all(&b"\
    ///    [cargo-update]\n\
    ///    default_features = true\n\
    ///    features = [\"serde\"]\n"[..]);
    /// assert_eq!(PackageConfig::read(&config_file), Ok({
    ///     let mut pkgs = BTreeMap::new();
    ///     pkgs.insert("cargo-update".to_string(), PackageConfig {
    ///         toolchain: None,
    ///         default_features: true,
    ///         features: {
    ///             let mut feats = BTreeSet::new();
    ///             feats.insert("serde".to_string());
    ///             feats
    ///         },
    ///         debug: None,
    ///         install_prereleases: None,
    ///         enforce_lock: None,
    ///         respect_binaries: None,
    ///         target_version: None,
    ///     });
    ///     pkgs
    /// }));
    /// ```
    pub fn read(p: &Path) -> Result<BTreeMap<String, PackageConfig>, i32> {
        if p.exists() {
            let mut buf = String::new();
            File::open(p).map_err(|_| 1)?
                .read_to_string(&mut buf)
                .map_err(|_| 1)?;

            toml::from_str(&buf).map_err(|_| 2)
        } else {
            Ok(BTreeMap::new())
        }
    }

    /// Save a configset to the specified file.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::collections::{BTreeSet, BTreeMap};
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::fs::{File, create_dir_all};
    /// # use std::env::temp_dir;
    /// # use std::io::Read;
    /// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-write-0");
    /// # create_dir_all(&td).unwrap();
    /// # let config_file = td.join(".install_config.toml");
    /// PackageConfig::write(&{
    ///     let mut pkgs = BTreeMap::new();
    ///     pkgs.insert("cargo-update".to_string(), PackageConfig {
    ///         toolchain: None,
    ///         default_features: true,
    ///         features: {
    ///             let mut feats = BTreeSet::new();
    ///             feats.insert("serde".to_string());
    ///             feats
    ///         },
    ///         debug: None,
    ///         install_prereleases: None,
    ///         enforce_lock: None,
    ///         respect_binaries: None,
    ///         target_version: None,
    ///     });
    ///     pkgs
    /// }, &config_file).unwrap();
    ///
    /// let mut buf = String::new();
    /// File::open(&config_file).unwrap().read_to_string(&mut buf).unwrap();
    /// assert_eq!(&buf, "[cargo-update]\n\
    ///                   default_features = true\n\
    ///                   features = [\"serde\"]\n");
    /// ```
    pub fn write(configuration: &BTreeMap<String, PackageConfig>, p: &Path) -> Result<(), i32> {
        File::create(p)
            .map_err(|_| 3)?
            .write_all(&toml::to_vec(configuration).map_err(|_| 2)?)
            .map_err(|_| 3)
    }
}

impl Default for PackageConfig {
    fn default() -> PackageConfig {
        PackageConfig {
            toolchain: None,
            default_features: true,
            features: BTreeSet::new(),
            debug: None,
            install_prereleases: None,
            enforce_lock: None,
            respect_binaries: None,
            target_version: None,
        }
    }
}
<commit_msg>str::strip_suffix() is 1.45.0<commit_after>use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as FWrite;
use std::io::{Read, Write};
use std::default::Default;
use semver::VersionReq;
use std::borrow::Cow;
use std::path::Path;
use std::fs::File;
use toml;

/// A single operation to be executed upon configuration of a package.
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ConfigOperation {
    /// Set the toolchain to use to compile the package.
    SetToolchain(String),
    /// Use the default toolchain to use to compile the package.
    RemoveToolchain,
    /// Whether to compile the package with the default features.
    DefaultFeatures(bool),
    /// Compile the package with the specified feature.
    AddFeature(String),
    /// Remove the feature from the list of features to compile with.
    RemoveFeature(String),
    /// Set debug mode being enabled to the specified value.
    SetDebugMode(bool),
    /// Set allowing to install prereleases to the specified value.
    SetInstallPrereleases(bool),
    /// Set enforcing Cargo.lock to the specified value.
    SetEnforceLock(bool),
    /// Set installing only the pre-set binaries.
    SetRespectBinaries(bool),
    /// Constrain the installed to the specified one.
    SetTargetVersion(VersionReq),
    /// Always install latest package version.
    RemoveTargetVersion,
}

/// Compilation configuration for one crate.
///
/// # Examples
///
/// Reading a configset, adding an entry to it, then writing it back.
///
/// ```
/// # use cargo_update::ops::PackageConfig;
/// # use std::fs::{File, create_dir_all};
/// # use std::env::temp_dir;
/// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-0");
/// # create_dir_all(&td).unwrap();
/// # let config_file = td.join(".install_config.toml");
/// # let operations = [];
/// let mut configuration = PackageConfig::read(&config_file).unwrap();
/// configuration.insert("cargo_update".to_string(), PackageConfig::from(&operations));
/// PackageConfig::write(&configuration, &config_file).unwrap();
/// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PackageConfig {
    /// Toolchain to use to compile the package, or `None` for default.
    pub toolchain: Option<String>,
    /// Whether to compile the package with the default features.
    pub default_features: bool,
    /// Features to compile the package with.
    pub features: BTreeSet<String>,
    /// Whether to compile in debug mode.
    pub debug: Option<bool>,
    /// Whether to install pre-release versions.
    pub install_prereleases: Option<bool>,
    /// Whether to enforce Cargo.lock versions.
    pub enforce_lock: Option<bool>,
    /// Whether to install only the pre-configured binaries.
    pub respect_binaries: Option<bool>,
    /// Versions to constrain to.
    pub target_version: Option<VersionReq>,
}

impl PackageConfig {
    /// Create a package config based on the default settings and modified according to the specified operations.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_update;
    /// # extern crate semver;
    /// # fn main() {
    /// # use cargo_update::ops::{ConfigOperation, PackageConfig};
    /// # use std::collections::BTreeSet;
    /// # use semver::VersionReq;
    /// # use std::str::FromStr;
    /// assert_eq!(PackageConfig::from(&[ConfigOperation::SetToolchain("nightly".to_string()),
    ///                                  ConfigOperation::DefaultFeatures(false),
    ///                                  ConfigOperation::AddFeature("rustc-serialize".to_string()),
    ///                                  ConfigOperation::SetDebugMode(true),
    ///                                  ConfigOperation::SetInstallPrereleases(false),
    ///                                  ConfigOperation::SetEnforceLock(true),
    ///                                  ConfigOperation::SetRespectBinaries(true),
    ///                                  ConfigOperation::SetTargetVersion(VersionReq::from_str(">=0.1").unwrap())]),
    ///            PackageConfig {
    ///                toolchain: Some("nightly".to_string()),
    ///                default_features: false,
    ///                features: {
    ///                    let mut feats = BTreeSet::new();
    ///                    feats.insert("rustc-serialize".to_string());
    ///                    feats
    ///                },
    ///                debug: Some(true),
    ///                install_prereleases: Some(false),
    ///                enforce_lock: Some(true),
    ///                respect_binaries: Some(true),
    ///                target_version: Some(VersionReq::from_str(">=0.1").unwrap()),
    ///            });
    /// # }
    /// ```
    pub fn from<'o, O: IntoIterator<Item = &'o ConfigOperation>>(ops: O) -> PackageConfig {
        let mut def = PackageConfig::default();
        def.execute_operations(ops);
        def
    }

    /// Generate cargo arguments from this configuration.
    ///
    /// Executable names are stripped of their trailing `".exe"`, if any.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::collections::BTreeMap;
    /// # use std::process::Command;
    /// # let name = "cargo-update".to_string();
    /// # let mut configuration = BTreeMap::new();
    /// # configuration.insert(name.clone(), PackageConfig::from(&[]));
    /// let cmd = Command::new("cargo").args(configuration.get(&name).unwrap().cargo_args(&["racer"]).iter().map(AsRef::as_ref)).arg(&name)
    /// // Process the command further -- run it, for example.
    /// # .status().unwrap();
    /// # let _ = cmd;
    /// ```
    pub fn cargo_args<S: AsRef<str>, I: IntoIterator<Item = S>>(&self, executables: I) -> Vec<Cow<'static, str>> {
        let mut res = vec![];
        if let Some(ref t) = self.toolchain {
            res.push(format!("+{}", t).into());
        }
        res.push("install".into());
        res.push("-f".into());
        if !self.default_features {
            res.push("--no-default-features".into());
        }
        if !self.features.is_empty() {
            res.push("--features".into());
            let mut a = String::new();
            for f in &self.features {
                write!(a, "{} ", f).unwrap();
            }
            res.push(a.into());
        }
        if let Some(true) = self.enforce_lock {
            res.push("--locked".into());
        }
        if let Some(true) = self.respect_binaries {
            for x in executables {
                let x = x.as_ref();

                res.push("--bin".into());
                res.push(if x.ends_with(".exe") {
                    &x[..x.len() - 4]
                } else {
                    x
                }.to_string().into());
            }
        }
        if let Some(true) = self.debug {
            res.push("--debug".into());
        }
        res
    }

    /// Modify `self` according to the specified set of operations.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_update;
    /// # extern crate semver;
    /// # fn main() {
    /// # use cargo_update::ops::{ConfigOperation, PackageConfig};
    /// # use std::collections::BTreeSet;
    /// # use semver::VersionReq;
    /// # use std::str::FromStr;
    /// let mut cfg = PackageConfig {
    ///     toolchain: Some("nightly".to_string()),
    ///     default_features: false,
    ///     features: {
    ///         let mut feats = BTreeSet::new();
    ///         feats.insert("rustc-serialize".to_string());
    ///         feats
    ///     },
    ///     debug: None,
    ///     install_prereleases: None,
    ///     enforce_lock: None,
    ///     respect_binaries: None,
    ///     target_version: Some(VersionReq::from_str(">=0.1").unwrap()),
    /// };
    /// cfg.execute_operations(&[ConfigOperation::RemoveToolchain,
    ///                          ConfigOperation::AddFeature("serde".to_string()),
    ///                          ConfigOperation::RemoveFeature("rustc-serialize".to_string()),
    ///                          ConfigOperation::SetDebugMode(true),
    ///                          ConfigOperation::RemoveTargetVersion]);
    /// assert_eq!(cfg,
    ///            PackageConfig {
    ///                toolchain: None,
    ///                default_features: false,
    ///                features: {
    ///                    let mut feats = BTreeSet::new();
    ///                    feats.insert("serde".to_string());
    ///                    feats
    ///                },
    ///                debug: Some(true),
    ///                install_prereleases: None,
    ///                enforce_lock: None,
    ///                respect_binaries: None,
    ///                target_version: None,
    ///            });
    /// # }
    /// ```
    pub fn execute_operations<'o, O: IntoIterator<Item = &'o ConfigOperation>>(&mut self, ops: O) {
        for op in ops {
            self.execute_operation(op)
        }
    }

    fn execute_operation(&mut self, op: &ConfigOperation) {
        match *op {
            ConfigOperation::SetToolchain(ref tchn) => self.toolchain = Some(tchn.clone()),
            ConfigOperation::RemoveToolchain => self.toolchain = None,
            ConfigOperation::DefaultFeatures(f) => self.default_features = f,
            ConfigOperation::AddFeature(ref feat) => {
                self.features.insert(feat.clone());
            }
            ConfigOperation::RemoveFeature(ref feat) => {
                self.features.remove(feat);
            }
            ConfigOperation::SetDebugMode(d) => self.debug = Some(d),
            ConfigOperation::SetInstallPrereleases(pr) => self.install_prereleases = Some(pr),
            ConfigOperation::SetEnforceLock(el) => self.enforce_lock = Some(el),
            ConfigOperation::SetRespectBinaries(rb) => self.respect_binaries = Some(rb),
            ConfigOperation::SetTargetVersion(ref vr) => self.target_version = Some(vr.clone()),
            ConfigOperation::RemoveTargetVersion => self.target_version = None,
        }
    }

    /// Read a configset from the specified file.
    ///
    /// If the specified file doesn't exist an empty configset is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::collections::{BTreeSet, BTreeMap};
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::fs::{File, create_dir_all};
    /// # use std::env::temp_dir;
    /// # use std::io::Write;
    /// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-read-0");
    /// # create_dir_all(&td).unwrap();
    /// # let config_file = td.join(".install_config.toml");
    /// File::create(&config_file).unwrap().write_all(&b"\
    ///    [cargo-update]\n\
    ///    default_features = true\n\
    ///    features = [\"serde\"]\n"[..]);
    /// assert_eq!(PackageConfig::read(&config_file), Ok({
    ///     let mut pkgs = BTreeMap::new();
    ///     pkgs.insert("cargo-update".to_string(), PackageConfig {
    ///         toolchain: None,
    ///         default_features: true,
    ///         features: {
    ///             let mut feats = BTreeSet::new();
    ///             feats.insert("serde".to_string());
    ///             feats
    ///         },
    ///         debug: None,
    ///         install_prereleases: None,
    ///         enforce_lock: None,
    ///         respect_binaries: None,
    ///         target_version: None,
    ///     });
    ///     pkgs
    /// }));
    /// ```
    pub fn read(p: &Path) -> Result<BTreeMap<String, PackageConfig>, i32> {
        if p.exists() {
            let mut buf = String::new();
            File::open(p).map_err(|_| 1)?
                .read_to_string(&mut buf)
                .map_err(|_| 1)?;

            toml::from_str(&buf).map_err(|_| 2)
        } else {
            Ok(BTreeMap::new())
        }
    }

    /// Save a configset to the specified file.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::collections::{BTreeSet, BTreeMap};
    /// # use cargo_update::ops::PackageConfig;
    /// # use std::fs::{File, create_dir_all};
    /// # use std::env::temp_dir;
    /// # use std::io::Read;
    /// # let td = temp_dir().join("cargo_update-doctest").join("PackageConfig-write-0");
    /// # create_dir_all(&td).unwrap();
    /// # let config_file = td.join(".install_config.toml");
    /// PackageConfig::write(&{
    ///     let mut pkgs = BTreeMap::new();
    ///     pkgs.insert("cargo-update".to_string(), PackageConfig {
    ///         toolchain: None,
    ///         default_features: true,
    ///         features: {
    ///             let mut feats = BTreeSet::new();
    ///             feats.insert("serde".to_string());
    ///             feats
    ///         },
    ///         debug: None,
    ///         install_prereleases: None,
    ///         enforce_lock: None,
    ///         respect_binaries: None,
    ///         target_version: None,
    ///     });
    ///     pkgs
    /// }, &config_file).unwrap();
    ///
    /// let mut buf = String::new();
    /// File::open(&config_file).unwrap().read_to_string(&mut buf).unwrap();
    /// assert_eq!(&buf, "[cargo-update]\n\
    ///                   default_features = true\n\
    ///                   features = [\"serde\"]\n");
    /// ```
    pub fn write(configuration: &BTreeMap<String, PackageConfig>, p: &Path) -> Result<(), i32> {
        File::create(p)
            .map_err(|_| 3)?
            .write_all(&toml::to_vec(configuration).map_err(|_| 2)?)
            .map_err(|_| 3)
    }
}

impl Default for PackageConfig {
    fn default() -> PackageConfig {
        PackageConfig {
            toolchain: None,
            default_features: true,
            features: BTreeSet::new(),
            debug: None,
            install_prereleases: None,
            enforce_lock: None,
            respect_binaries: None,
            target_version: None,
        }
    }
}
<|endoftext|>