<commit_before>use crate::utils::ptr::get_spans;
use crate::utils::{
    get_trait_def_id, implements_trait, is_copy, is_self, is_type_diagnostic_item, multispan_sugg, paths, snippet,
    snippet_opt, span_lint_and_then,
};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, ItemKind, Impl, Node, PatKind, QPath, TyKind};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, TypeFoldable};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::misc::can_type_implement_copy;
use rustc_typeck::expr_use_visitor as euv;
use std::borrow::Cow;

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by value, but not
    /// consuming them in its
    /// body.
    ///
    /// **Why is this bad?** Taking arguments by reference is more flexible and can
    /// sometimes avoid
    /// unnecessary allocations.
    ///
    /// **Known problems:**
    /// * This lint suggests taking an argument by reference,
    /// however sometimes it is better to let users decide the argument type
    /// (by using `Borrow` trait, for example), depending on how the function is used.
    ///
    /// **Example:**
    /// ```rust
    /// fn foo(v: Vec<i32>) {
    ///     assert_eq!(v.len(), 42);
    /// }
    /// ```
    /// should be
    /// ```rust
    /// fn foo(v: &[i32]) {
    ///     assert_eq!(v.len(), 42);
    /// }
    /// ```
    pub NEEDLESS_PASS_BY_VALUE,
    pedantic,
    "functions taking arguments by value, but not consuming them in its body"
}

declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]);

macro_rules! need {
    ($e: expr) => {
        if let Some(x) = $e {
            x
        } else {
            return;
        }
    };
}

impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
    #[allow(clippy::too_many_lines)]
    fn check_fn(
        &mut self,
        cx: &LateContext<'tcx>,
        kind: FnKind<'tcx>,
        decl: &'tcx FnDecl<'_>,
        body: &'tcx Body<'_>,
        span: Span,
        hir_id: HirId,
    ) {
        if span.from_expansion() {
            return;
        }

        match kind {
            FnKind::ItemFn(.., header, _, attrs) => {
                if header.abi != Abi::Rust || requires_exact_signature(attrs) {
                    return;
                }
            },
            FnKind::Method(..) => (),
            FnKind::Closure(..) => return,
        }

        // Exclude non-inherent impls
        if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
            if matches!(
                item.kind,
                ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
            ) {
                return;
            }
        }

        // Allow `Borrow` or functions to be taken by value
        let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
        let allowed_traits = [
            need!(cx.tcx.lang_items().fn_trait()),
            need!(cx.tcx.lang_items().fn_once_trait()),
            need!(cx.tcx.lang_items().fn_mut_trait()),
            need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)),
        ];

        let sized_trait = need!(cx.tcx.lang_items().sized_trait());

        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);

        let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
            .filter(|p| !p.is_global())
            .filter_map(|obligation| {
                // Note that we do not want to deal with qualified predicates here.
                let ty::PredicateKind::ForAll(binder) = obligation.predicate.kind();
                match binder.skip_binder() {
                    ty::PredicateAtom::Trait(pred, _) if !binder.has_escaping_bound_vars() => {
                        if pred.def_id() == sized_trait {
                            return None;
                        }
                        Some(pred)
                    }
                    _ => None,
                }
            })
            .collect::<Vec<_>>();

        // Collect moved variables and spans which will need dereferencings from the
        // function body.
        let MovedVariablesCtxt {
            moved_vars,
            spans_need_deref,
            ..
        } = {
            let mut ctx = MovedVariablesCtxt::default();
            cx.tcx.infer_ctxt().enter(|infcx| {
                euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
                    .consume_body(body);
            });
            ctx
        };

        let fn_sig = cx.tcx.fn_sig(fn_def_id);
        let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);

        for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() {
            // All spans generated from a proc-macro invocation are the same...
            if span == input.span {
                return;
            }

            // Ignore `self`s.
            if idx == 0 {
                if let PatKind::Binding(.., ident, _) = arg.pat.kind {
                    if ident.as_str() == "self" {
                        continue;
                    }
                }
            }

            //
            // * Exclude a type that is specifically bounded by `Borrow`.
            // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
            //   `serde::Serialize`)
            let (implements_borrow_trait, all_borrowable_trait) = {
                let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::<Vec<_>>();

                (
                    preds.iter().any(|t| t.def_id() == borrow_trait),
                    !preds.is_empty() && {
                        let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty);
                        preds.iter().all(|t| {
                            let ty_params = t.trait_ref.substs.iter().skip(1).collect::<Vec<_>>();
                            implements_trait(cx, ty_empty_region, t.def_id(), &ty_params)
                        })
                    },
                )
            };

            if_chain! {
                if !is_self(arg);
                if !ty.is_mutable_ptr();
                if !is_copy(cx, ty);
                if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[]));
                if !implements_borrow_trait;
                if !all_borrowable_trait;

                if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind;
                if !moved_vars.contains(&canonical_id);
                then {
                    if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
                        continue;
                    }

                    // Dereference suggestion
                    let sugg = |diag: &mut DiagnosticBuilder<'_>| {
                        if let ty::Adt(def, ..) = ty.kind() {
                            if let Some(span) = cx.tcx.hir().span_if_local(def.did) {
                                if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() {
                                    diag.span_help(span, "consider marking this type as `Copy`");
                                }
                            }
                        }

                        let deref_span = spans_need_deref.get(&canonical_id);
                        if_chain! {
                            if is_type_diagnostic_item(cx, ty, sym::vec_type);
                            if let Some(clone_spans) =
                                get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]);
                            if let TyKind::Path(QPath::Resolved(_, ref path)) = input.kind;
                            if let Some(elem_ty) = path.segments.iter()
                                .find(|seg| seg.ident.name == sym::Vec)
                                .and_then(|ps| ps.args.as_ref())
                                .map(|params| params.args.iter().find_map(|arg| match arg {
                                    GenericArg::Type(ty) => Some(ty),
                                    _ => None,
                                }).unwrap());
                            then {
                                let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
                                diag.span_suggestion(
                                    input.span,
                                    "consider changing the type to",
                                    slice_ty,
                                    Applicability::Unspecified,
                                );

                                for (span, suggestion) in clone_spans {
                                    diag.span_suggestion(
                                        span,
                                        &snippet_opt(cx, span)
                                            .map_or(
                                                "change the call to".into(),
                                                |x| Cow::from(format!("change `{}` to", x)),
                                            ),
                                        suggestion.into(),
                                        Applicability::Unspecified,
                                    );
                                }

                                // cannot be destructured, no need for `*` suggestion
                                assert!(deref_span.is_none());
                                return;
                            }
                        }

                        if is_type_diagnostic_item(cx, ty, sym::string_type) {
                            if let Some(clone_spans) =
                                get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
                                diag.span_suggestion(
                                    input.span,
                                    "consider changing the type to",
                                    "&str".to_string(),
                                    Applicability::Unspecified,
                                );

                                for (span, suggestion) in clone_spans {
                                    diag.span_suggestion(
                                        span,
                                        &snippet_opt(cx, span)
                                            .map_or(
                                                "change the call to".into(),
                                                |x| Cow::from(format!("change `{}` to", x))
                                            ),
                                        suggestion.into(),
                                        Applicability::Unspecified,
                                    );
                                }

                                assert!(deref_span.is_none());
                                return;
                            }
                        }

                        let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];

                        // Suggests adding `*` to dereference the added reference.
                        if let Some(deref_span) = deref_span {
                            spans.extend(
                                deref_span
                                    .iter()
                                    .cloned()
                                    .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
                            );
                            spans.sort_by_key(|&(span, _)| span);
                        }
                        multispan_sugg(diag, "consider taking a reference instead", spans);
                    };

                    span_lint_and_then(
                        cx,
                        NEEDLESS_PASS_BY_VALUE,
                        input.span,
                        "this argument is passed by value, but not consumed in the function body",
                        sugg,
                    );
                }
            }
        }
    }
}

/// Functions marked with these attributes must have the exact signature.
fn requires_exact_signature(attrs: &[Attribute]) -> bool {
    attrs.iter().any(|attr| {
        [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
            .iter()
            .any(|&allow| attr.has_name(allow))
    })
}

#[derive(Default)]
struct MovedVariablesCtxt {
    moved_vars: FxHashSet<HirId>,
    /// Spans which need to be prefixed with `*` for dereferencing the
    /// suggested additional reference.
    spans_need_deref: FxHashMap<HirId, FxHashSet<Span>>,
}

impl MovedVariablesCtxt {
    fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) {
        if let euv::PlaceBase::Local(vid) = cmt.place.base {
            self.moved_vars.insert(vid);
        }
    }
}

impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt {
    fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId, mode: euv::ConsumeMode) {
        if let euv::ConsumeMode::Move = mode {
            self.move_common(cmt);
        }
    }

    fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}

    fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
}
<commit_msg>Remove PredicateKind<commit_after>use crate::utils::ptr::get_spans;
use crate::utils::{
    get_trait_def_id, implements_trait, is_copy, is_self, is_type_diagnostic_item, multispan_sugg, paths, snippet,
    snippet_opt, span_lint_and_then,
};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, ItemKind, Impl, Node, PatKind, QPath, TyKind};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, TypeFoldable};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::misc::can_type_implement_copy;
use rustc_typeck::expr_use_visitor as euv;
use std::borrow::Cow;

declare_clippy_lint! {
    /// **What it does:** Checks for functions taking arguments by value, but not
    /// consuming them in its
    /// body.
    ///
    /// **Why is this bad?** Taking arguments by reference is more flexible and can
    /// sometimes avoid
    /// unnecessary allocations.
    ///
    /// **Known problems:**
    /// * This lint suggests taking an argument by reference,
    /// however sometimes it is better to let users decide the argument type
    /// (by using `Borrow` trait, for example), depending on how the function is used.
    ///
    /// **Example:**
    /// ```rust
    /// fn foo(v: Vec<i32>) {
    ///     assert_eq!(v.len(), 42);
    /// }
    /// ```
    /// should be
    /// ```rust
    /// fn foo(v: &[i32]) {
    ///     assert_eq!(v.len(), 42);
    /// }
    /// ```
    pub NEEDLESS_PASS_BY_VALUE,
    pedantic,
    "functions taking arguments by value, but not consuming them in its body"
}

declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]);

macro_rules! need {
    ($e: expr) => {
        if let Some(x) = $e {
            x
        } else {
            return;
        }
    };
}

impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
    #[allow(clippy::too_many_lines)]
    fn check_fn(
        &mut self,
        cx: &LateContext<'tcx>,
        kind: FnKind<'tcx>,
        decl: &'tcx FnDecl<'_>,
        body: &'tcx Body<'_>,
        span: Span,
        hir_id: HirId,
    ) {
        if span.from_expansion() {
            return;
        }

        match kind {
            FnKind::ItemFn(.., header, _, attrs) => {
                if header.abi != Abi::Rust || requires_exact_signature(attrs) {
                    return;
                }
            },
            FnKind::Method(..) => (),
            FnKind::Closure(..) => return,
        }

        // Exclude non-inherent impls
        if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
            if matches!(
                item.kind,
                ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
            ) {
                return;
            }
        }

        // Allow `Borrow` or functions to be taken by value
        let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT));
        let allowed_traits = [
            need!(cx.tcx.lang_items().fn_trait()),
            need!(cx.tcx.lang_items().fn_once_trait()),
            need!(cx.tcx.lang_items().fn_mut_trait()),
            need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)),
        ];

        let sized_trait = need!(cx.tcx.lang_items().sized_trait());

        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);

        let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
            .filter(|p| !p.is_global())
            .filter_map(|obligation| {
                // Note that we do not want to deal with qualified predicates here.
                let binder = obligation.predicate.kind();
                match binder.skip_binder() {
                    ty::PredicateAtom::Trait(pred, _) if !binder.has_escaping_bound_vars() => {
                        if pred.def_id() == sized_trait {
                            return None;
                        }
                        Some(pred)
                    }
                    _ => None,
                }
            })
            .collect::<Vec<_>>();

        // Collect moved variables and spans which will need dereferencings from the
        // function body.
        let MovedVariablesCtxt {
            moved_vars,
            spans_need_deref,
            ..
        } = {
            let mut ctx = MovedVariablesCtxt::default();
            cx.tcx.infer_ctxt().enter(|infcx| {
                euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
                    .consume_body(body);
            });
            ctx
        };

        let fn_sig = cx.tcx.fn_sig(fn_def_id);
        let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);

        for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() {
            // All spans generated from a proc-macro invocation are the same...
            if span == input.span {
                return;
            }

            // Ignore `self`s.
            if idx == 0 {
                if let PatKind::Binding(.., ident, _) = arg.pat.kind {
                    if ident.as_str() == "self" {
                        continue;
                    }
                }
            }

            //
            // * Exclude a type that is specifically bounded by `Borrow`.
            // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
            //   `serde::Serialize`)
            let (implements_borrow_trait, all_borrowable_trait) = {
                let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::<Vec<_>>();

                (
                    preds.iter().any(|t| t.def_id() == borrow_trait),
                    !preds.is_empty() && {
                        let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty);
                        preds.iter().all(|t| {
                            let ty_params = t.trait_ref.substs.iter().skip(1).collect::<Vec<_>>();
                            implements_trait(cx, ty_empty_region, t.def_id(), &ty_params)
                        })
                    },
                )
            };

            if_chain! {
                if !is_self(arg);
                if !ty.is_mutable_ptr();
                if !is_copy(cx, ty);
                if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[]));
                if !implements_borrow_trait;
                if !all_borrowable_trait;

                if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind;
                if !moved_vars.contains(&canonical_id);
                then {
                    if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
                        continue;
                    }

                    // Dereference suggestion
                    let sugg = |diag: &mut DiagnosticBuilder<'_>| {
                        if let ty::Adt(def, ..) = ty.kind() {
                            if let Some(span) = cx.tcx.hir().span_if_local(def.did) {
                                if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() {
                                    diag.span_help(span, "consider marking this type as `Copy`");
                                }
                            }
                        }

                        let deref_span = spans_need_deref.get(&canonical_id);
                        if_chain! {
                            if is_type_diagnostic_item(cx, ty, sym::vec_type);
                            if let Some(clone_spans) =
                                get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]);
                            if let TyKind::Path(QPath::Resolved(_, ref path)) = input.kind;
                            if let Some(elem_ty) = path.segments.iter()
                                .find(|seg| seg.ident.name == sym::Vec)
                                .and_then(|ps| ps.args.as_ref())
                                .map(|params| params.args.iter().find_map(|arg| match arg {
                                    GenericArg::Type(ty) => Some(ty),
                                    _ => None,
                                }).unwrap());
                            then {
                                let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
                                diag.span_suggestion(
                                    input.span,
                                    "consider changing the type to",
                                    slice_ty,
                                    Applicability::Unspecified,
                                );

                                for (span, suggestion) in clone_spans {
                                    diag.span_suggestion(
                                        span,
                                        &snippet_opt(cx, span)
                                            .map_or(
                                                "change the call to".into(),
                                                |x| Cow::from(format!("change `{}` to", x)),
                                            ),
                                        suggestion.into(),
                                        Applicability::Unspecified,
                                    );
                                }

                                // cannot be destructured, no need for `*` suggestion
                                assert!(deref_span.is_none());
                                return;
                            }
                        }

                        if is_type_diagnostic_item(cx, ty, sym::string_type) {
                            if let Some(clone_spans) =
                                get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
                                diag.span_suggestion(
                                    input.span,
                                    "consider changing the type to",
                                    "&str".to_string(),
                                    Applicability::Unspecified,
                                );

                                for (span, suggestion) in clone_spans {
                                    diag.span_suggestion(
                                        span,
                                        &snippet_opt(cx, span)
                                            .map_or(
                                                "change the call to".into(),
                                                |x| Cow::from(format!("change `{}` to", x))
                                            ),
                                        suggestion.into(),
                                        Applicability::Unspecified,
                                    );
                                }

                                assert!(deref_span.is_none());
                                return;
                            }
                        }

                        let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];

                        // Suggests adding `*` to dereference the added reference.
                        if let Some(deref_span) = deref_span {
                            spans.extend(
                                deref_span
                                    .iter()
                                    .cloned()
                                    .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
                            );
                            spans.sort_by_key(|&(span, _)| span);
                        }
                        multispan_sugg(diag, "consider taking a reference instead", spans);
                    };

                    span_lint_and_then(
                        cx,
                        NEEDLESS_PASS_BY_VALUE,
                        input.span,
                        "this argument is passed by value, but not consumed in the function body",
                        sugg,
                    );
                }
            }
        }
    }
}

/// Functions marked with these attributes must have the exact signature.
fn requires_exact_signature(attrs: &[Attribute]) -> bool {
    attrs.iter().any(|attr| {
        [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
            .iter()
            .any(|&allow| attr.has_name(allow))
    })
}

#[derive(Default)]
struct MovedVariablesCtxt {
    moved_vars: FxHashSet<HirId>,
    /// Spans which need to be prefixed with `*` for dereferencing the
    /// suggested additional reference.
    spans_need_deref: FxHashMap<HirId, FxHashSet<Span>>,
}

impl MovedVariablesCtxt {
    fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) {
        if let euv::PlaceBase::Local(vid) = cmt.place.base {
            self.moved_vars.insert(vid);
        }
    }
}

impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt {
    fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId, mode: euv::ConsumeMode) {
        if let euv::ConsumeMode::Move = mode {
            self.move_common(cmt);
        }
    }

    fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}

    fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
}
<|endoftext|>


<commit_before>use crate::{error::Result,
            RegistryType};
use failure::SyncFailure;
use habitat_core::{package::{FullyQualifiedPackageIdent,
                             Identifiable},
                   ChannelIdent};
use handlebars::Handlebars;

/// An image naming policy.
///
/// This is a value struct which captures the naming and tagging intentions for an image.
#[derive(Debug, Default)]
pub struct Naming {
    /// An optional custom image name which would override a computed default value.
    custom_image_name:   Option<String>,
    /// Whether or not to tag the image with a latest value.
    latest_tag:          bool,
    /// Whether or not to tag the image with a value containing a version from a Package
    /// Identifier.
    version_tag:         bool,
    /// Whether or not to tag the image with a value containing a version and release from a
    /// Package Identifier.
    version_release_tag: bool,
    /// An optional custom tag value for the image.
    custom_tag:          Option<String>,

    // TODO (CM): I don't think either of these really belongs to this
    // Naming type

    // TODO (CM): really, url and type are conceptually part of a
    // single new type.
    /// A URL to a custom Docker registry to publish to. This will be used as part of every tag
    /// before pushing.
    pub registry_url:  Option<String>, // TODO (CM): Option<Url>
    /// The type of registry we're publishing to. Ex: Amazon, Docker, Google, Azure.
    pub registry_type: RegistryType,
}

// TODO (CM): must validate custom names and tags w/r/t tag spec
// https://docs.docker.com/engine/reference/commandline/tag/
//
// An image name is made up of slash-separated name components,
// optionally prefixed by a registry hostname. The hostname must
// comply with standard DNS rules, but may not contain underscores. If
// a hostname is present, it may optionally be followed by a port
// number in the format :8080. If not present, the command uses
// Docker’s public registry located at registry-1.docker.io by
// default. Name components may contain lowercase letters, digits and
// separators. A separator is defined as a period, one or two
// underscores, or one or more dashes. A name component may not start
// or end with a separator.
//
// A tag name must be valid ASCII and may contain lowercase and
// uppercase letters, digits, underscores, periods and dashes. A tag
// name may not start with a period or a dash and may contain a
// maximum of 128 characters.

impl Naming {
    /// Creates a `Naming` from cli arguments.
    pub fn new_from_cli_matches(m: &clap::ArgMatches) -> Self {
        let registry_type =
            clap::value_t!(m.value_of("REGISTRY_TYPE"), RegistryType).unwrap_or_default();

        // TODO (CM): If registry_type is Docker, we must set this to
        // dockerhub. Otherwise, it MUST be present, because of how
        // clap is set up.
        let registry_url = m.value_of("REGISTRY_URL").map(ToString::to_string);

        Naming { custom_image_name: m.value_of("IMAGE_NAME").map(ToString::to_string),
                 latest_tag: !m.is_present("NO_TAG_LATEST"),
                 version_tag: !m.is_present("NO_TAG_VERSION"),
                 version_release_tag: !m.is_present("NO_TAG_VERSION_RELEASE"),
                 custom_tag: m.value_of("TAG_CUSTOM").map(ToString::to_string),
                 registry_url,
                 registry_type }
    }

    pub fn image_identifiers(&self,
                             ident: &FullyQualifiedPackageIdent,
                             channel: &ChannelIdent)
                             -> Result<(String, Vec<String>)> {
        let name = self.image_name(ident, channel)?;
        let tags = vec![self.latest_tag(),
                        self.version_tag(&ident),
                        self.version_release_tag(&ident),
                        self.custom_tag(ident, channel)?].into_iter()
                                                         .filter_map(|e| e)
                                                         .collect();
        Ok((name, tags))
    }

    fn image_name(&self,
                  ident: &FullyQualifiedPackageIdent,
                  channel: &ChannelIdent)
                  -> Result<String> {
        let json = Self::json_payload(ident, channel);

        let image_name = match self.custom_image_name {
            Some(ref custom) => {
                Handlebars::new().template_render(custom, &json)
                                 .map_err(SyncFailure::new)?
            }
            None => format!("{}/{}", ident.origin(), ident.name()),
        };

        // TODO (CM): perhaps we should prepend the registry URL to
        // tags only if we going to push (and at the time we are
        // pushing, not here)
        let image_name = match self.registry_url {
            Some(ref url) => format!("{}/{}", url, image_name),
            None => image_name,
        };

        Ok(image_name.to_lowercase())
    }

    fn version_release_tag(&self, ident: &FullyQualifiedPackageIdent) -> Option<String> {
        if self.version_release_tag {
            Some(format!("{}-{}",
                         FullyQualifiedPackageIdent::version(ident),
                         FullyQualifiedPackageIdent::release(ident)))
        } else {
            None
        }
    }

    fn version_tag(&self, ident: &FullyQualifiedPackageIdent) -> Option<String> {
        if self.version_tag {
            Some(FullyQualifiedPackageIdent::version(ident).to_string())
        } else {
            None
        }
    }

    // TODO (CM): not sure how useful this is, since I think "latest"
    // is *always* created.
    fn latest_tag(&self) -> Option<String> {
        if self.latest_tag {
            Some("latest".to_string())
        } else {
            None
        }
    }

    fn custom_tag(&self,
                  ident: &FullyQualifiedPackageIdent,
                  channel: &ChannelIdent)
                  -> Result<Option<String>> {
        if let Some(ref custom) = self.custom_tag {
            let json = Self::json_payload(ident, channel);
            let tag = Handlebars::new().template_render(&custom, &json)
                                       .map_err(SyncFailure::new)
                                       .map(|s| s.to_lowercase())?;
            Ok(Some(tag))
        } else {
            Ok(None)
        }
    }

    fn json_payload(ident: &FullyQualifiedPackageIdent,
                    channel: &ChannelIdent)
                    -> serde_json::Value {
        json!({
            "pkg_origin": ident.origin(),
            "pkg_name": ident.name(),
            "pkg_version": ident.version(),
            "pkg_release": ident.release(),
            "channel": channel,
        })
    }
}

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

    fn ident() -> FullyQualifiedPackageIdent { "core/foo/1.2.3/20200430153200".parse().unwrap() }

    #[test]
    fn default_naming_policy() {
        let naming = Naming::default();
        let ident = ident();
        let channel = ChannelIdent::default();

        assert!(naming.latest_tag().is_none());
        assert!(naming.custom_tag(&ident, &channel).unwrap().is_none());
        assert!(naming.version_tag(&ident).is_none());
        assert!(naming.version_release_tag(&ident).is_none());

        assert_eq!(naming.image_name(&ident, &channel).unwrap(), "core/foo");
    }

    #[test]
    fn latest_tag() {
        let mut naming = Naming::default();
        naming.latest_tag = true;
        assert_eq!(naming.latest_tag().unwrap(), "latest");
    }

    #[test]
    fn version_tag() {
        let mut naming = Naming::default();
        naming.version_tag = true;
        assert_eq!(naming.version_tag(&ident()).unwrap(), "1.2.3");
    }

    #[test]
    fn version_release_tag() {
        let mut naming = Naming::default();
        naming.version_release_tag = true;
        assert_eq!(naming.version_release_tag(&ident()).unwrap(),
                   "1.2.3-20200430153200");
    }

    #[test]
    fn image_name_with_registry_url() {
        let ident = ident();
        let channel = ChannelIdent::default();

        let mut naming = Naming::default();
        // TODO (CM): IMPLEMENTATION QUIRK
        // Registry type has no bearing on this! Fix it!
        naming.registry_url = Some(String::from("registry.mycompany.com:8080/v1"));

        let name = naming.image_name(&ident, &channel).unwrap();
        assert_eq!(name, "registry.mycompany.com:8080/v1/core/foo");
    }

    #[test]
    fn custom_image_names() {
        let ident = ident();
        let channel = ChannelIdent::default();

        // Template, Expected Result
        //
        // A "Some" expected result is something you expect to successfully
        // pass. A "None", on the other hand, is something you expect
        // to throw an error.
        let inputs = vec![

            // Valid inputs
            ("monkeys", Some("monkeys")),
            ("{{pkg_name}}", Some("foo")),
            ("{{pkg_origin}}-{{pkg_name}}", Some("core-foo")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}", Some("core-foo-1.2.3")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}",
             Some("core-foo-1.2.3-20200430153200")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}-{{channel}}",
             Some("core-foo-1.2.3-20200430153200-stable")),
            ("super-{{pkg_origin}}-wacky-{{pkg_name}}-funtime-{{pkg_version}}-container-{{pkg_release}}-party-{{channel}}-ohemgee",
             Some("super-core-wacky-foo-funtime-1.2.3-container-20200430153200-party-stable-ohemgee")),

            // Invalid inputs
            ("{{" , None),
            // ("{{not_a_valid_variable}}-{{pkg_name}}", None),
            // ("", None),
            // more examples of things that violate the tagging spec
        ];

        for (template, expected) in inputs {
            let mut naming = Naming::default();

            let template = String::from(template);
            naming.custom_image_name = Some(template.clone());
            let actual_name = naming.image_name(&ident, &channel);

            if let Some(expected_name) = expected {
                // expected success
                assert!(actual_name.is_ok());
                let actual_name = actual_name.unwrap();
                assert_eq!(actual_name, expected_name,
                           "Expected template '{}' to generate image name '{}', but it generated \
                            '{}'",
                           template, expected_name, actual_name);
            } else {
                // expected failure
                assert!(actual_name.is_err(),
                        "Expected template '{}' to fail to generate an image name, but it \
                         generated '{}'",
                        template,
                        actual_name.unwrap());
            }
        }
    }

    // TODO (CM): there is a bunch of duplication here :(

    #[test]
    fn custom_tag_names() {
        let ident = ident();
        let channel = ChannelIdent::default();

        // Template, Expected Result
        //
        // A "Some" expected result is something you expect to successfully
        // pass. A "None", on the other hand, is something you expect
        // to throw an error.
        let inputs = vec![

            // Valid inputs
            ("monkeys", Some("monkeys")),
            ("{{pkg_name}}", Some("foo")),
            ("{{pkg_origin}}-{{pkg_name}}", Some("core-foo")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}", Some("core-foo-1.2.3")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}",
             Some("core-foo-1.2.3-20200430153200")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}-{{channel}}",
             Some("core-foo-1.2.3-20200430153200-stable")),
            ("super-{{pkg_origin}}-wacky-{{pkg_name}}-funtime-{{pkg_version}}-container-{{pkg_release}}-party-{{channel}}-ohemgee",
             Some("super-core-wacky-foo-funtime-1.2.3-container-20200430153200-party-stable-ohemgee")),

            // Invalid inputs
            ("{{" , None),
            // ("{{not_a_valid_variable}}-{{pkg_name}}", None),
            // ("", None),
            // more examples of things that violate the tagging spec
        ];

        for (template, expected) in inputs {
            let mut naming = Naming::default();

            let template = String::from(template);
            naming.custom_tag = Some(template.clone());
            let actual_tag = naming.custom_tag(&ident, &channel);

            if let Some(expected_tag) = expected {
                // expected success
                assert!(actual_tag.is_ok());
                let actual_tag = actual_tag.unwrap().unwrap(); // Result<Option<String>>
                assert_eq!(actual_tag, expected_tag,
                           "Expected template '{}' to generate tag '{}', but it generated '{}'",
                           template, expected_tag, actual_tag);
            } else {
                // expected failure
                assert!(actual_tag.is_err(),
                        "Expected template '{}' to fail to generate a tag, but it generated '{}'",
                        template,
                        actual_tag.unwrap().unwrap());
            }
        }
    }

    #[test]
    fn default_image_identifiers() {
        let naming = Naming::default();
        let ident = ident();
        let channel = ChannelIdent::default();

        let (name, tags) = naming.image_identifiers(&ident, &channel).unwrap();

        assert_eq!(name, "core/foo");
        assert!(tags.is_empty());
    }

    #[test]
    fn all_the_image_identifiers() {
        let naming = Naming { custom_image_name:   Some(String::from("my-nifty/{{pkg_name}}")),
                              latest_tag:          true,
                              version_tag:         true,
                              version_release_tag: true,
                              custom_tag:          Some(String::from("new-hotness")),
                              registry_url:        Some(String::from("registry.mycompany.com:\
                                                                      8080/v1")),
                              registry_type:       RegistryType::Docker, };

        let ident = ident();
        let channel = ChannelIdent::default();

        let (name, tags) = naming.image_identifiers(&ident, &channel).unwrap();

        assert_eq!(name, "registry.mycompany.com:8080/v1/my-nifty/foo");
        assert_eq!(tags,
                   ["latest", "1.2.3", "1.2.3-20200430153200", "new-hotness"]);
    }
}
<commit_msg>Factor out all the Handlebars templating logic<commit_after>use crate::{error::Result,
            RegistryType};
use failure::SyncFailure;
use habitat_core::{package::{FullyQualifiedPackageIdent,
                             Identifiable},
                   ChannelIdent};
use handlebars::Handlebars;
use serde::Serialize;

const DEFAULT_IMAGE_NAME_TEMPLATE: &str = "{{pkg_origin}}/{{pkg_name}}";
const VERSION_RELEASE_TAG_TEMPLATE: &str = "{{pkg_version}}-{{pkg_release}}";
const VERSION_TAG_TEMPLATE: &str = "{{pkg_version}}";

/// Helper macro to mark all Handlebars templating calls that are not based
/// on user input, and can thus be safely unwrapped.
macro_rules! safe {
    ($render_result:expr) => {
        $render_result.expect("We are in control of all inputs to this rendering, and thus the \
                           result should always be safe to unwrap")
    };
}

/// An image naming policy.
///
/// This is a value struct which captures the naming and tagging intentions for an image.
#[derive(Debug, Default)]
pub struct Naming {
    /// An optional custom image name which would override a computed default value.
    custom_image_name:   Option<String>,
    /// Whether or not to tag the image with a latest value.
    latest_tag:          bool,
    /// Whether or not to tag the image with a value containing a version from a Package
    /// Identifier.
    version_tag:         bool,
    /// Whether or not to tag the image with a value containing a version and release from a
    /// Package Identifier.
    version_release_tag: bool,
    /// An optional custom tag value for the image.
    custom_tag:          Option<String>,

    // TODO (CM): I don't think either of these really belongs to this
    // Naming type

    // TODO (CM): really, url and type are conceptually part of a
    // single new type.
    /// A URL to a custom Docker registry to publish to. This will be used as part of every tag
    /// before pushing.
    pub registry_url:  Option<String>, // TODO (CM): Option<Url>
    /// The type of registry we're publishing to. Ex: Amazon, Docker, Google, Azure.
    pub registry_type: RegistryType,
}

// TODO (CM): must validate custom names and tags w/r/t tag spec
// https://docs.docker.com/engine/reference/commandline/tag/
//
// An image name is made up of slash-separated name components,
// optionally prefixed by a registry hostname. The hostname must
// comply with standard DNS rules, but may not contain underscores. If
// a hostname is present, it may optionally be followed by a port
// number in the format :8080. If not present, the command uses
// Docker’s public registry located at registry-1.docker.io by
// default. Name components may contain lowercase letters, digits and
// separators. A separator is defined as a period, one or two
// underscores, or one or more dashes. A name component may not start
// or end with a separator.
//
// A tag name must be valid ASCII and may contain lowercase and
// uppercase letters, digits, underscores, periods and dashes. A tag
// name may not start with a period or a dash and may contain a
// maximum of 128 characters.

impl Naming {
    /// Creates a `Naming` from cli arguments.
    pub fn new_from_cli_matches(m: &clap::ArgMatches) -> Self {
        let registry_type =
            clap::value_t!(m.value_of("REGISTRY_TYPE"), RegistryType).unwrap_or_default();

        // TODO (CM): If registry_type is Docker, we must set this to
        // dockerhub. Otherwise, it MUST be present, because of how
        // clap is set up.
        let registry_url = m.value_of("REGISTRY_URL").map(ToString::to_string);

        Naming { custom_image_name: m.value_of("IMAGE_NAME").map(ToString::to_string),
                 latest_tag: !m.is_present("NO_TAG_LATEST"),
                 version_tag: !m.is_present("NO_TAG_VERSION"),
                 version_release_tag: !m.is_present("NO_TAG_VERSION_RELEASE"),
                 custom_tag: m.value_of("TAG_CUSTOM").map(ToString::to_string),
                 registry_url,
                 registry_type }
    }

    // TODO (CM): I am skeptical of use of "channel" in any container
    // identifier, since that is not anything inherent to the package
    // we are containerizing.

    /// Return the image name, along with a (possibly empty) vector of
    /// additional tags.
    pub fn image_identifiers(&self,
                             ident: &FullyQualifiedPackageIdent,
                             channel: &ChannelIdent)
                             -> Result<(String, Vec<String>)> {
        let context = Self::rendering_context(ident, channel);

        let name = self.image_name(&context)?;
        let tags = vec![self.latest_tag(),
                        self.version_tag(&context),
                        self.version_release_tag(&context),
                        self.custom_tag(&context)?].into_iter()
                                                   .filter_map(|e| e)
                                                   .collect();
        Ok((name, tags))
    }

    ////////////////////////////////////////////////////////////////////////

    fn image_name<S>(&self, context: &S) -> Result<String>
        where S: Serialize
    {
        let image_name = if let Some(ref template) = self.custom_image_name {
            Self::render(&template, &context)?
        } else {
            safe!(Self::render(DEFAULT_IMAGE_NAME_TEMPLATE, &context))
        };

        // TODO (CM): perhaps we should prepend the registry URL to
        // tags only if we going to push (and at the time we are
        // pushing, not here)
        let image_name = match self.registry_url {
            Some(ref url) => format!("{}/{}", url, image_name),
            None => image_name,
        };

        Ok(image_name.to_lowercase())
    }

    fn version_release_tag<S>(&self, context: &S) -> Option<String>
        where S: Serialize
    {
        if self.version_release_tag {
            Some(safe!(Self::render(VERSION_RELEASE_TAG_TEMPLATE, &context)))
        } else {
            None
        }
    }

    fn version_tag<S>(&self, context: &S) -> Option<String>
        where S: Serialize
    {
        if self.version_tag {
            Some(safe!(Self::render(VERSION_TAG_TEMPLATE, &context)))
        } else {
            None
        }
    }

    // TODO (CM): not sure how useful this is, since I think "latest"
    // is *always* created.
    fn latest_tag(&self) -> Option<String> {
        if self.latest_tag {
            Some("latest".to_string())
        } else {
            None
        }
    }

    fn custom_tag<S>(&self, context: &S) -> Result<Option<String>>
        where S: Serialize
    {
        if let Some(ref custom_tag_template) = self.custom_tag {
            Ok(Some(Self::render(custom_tag_template, &context)?))
        } else {
            Ok(None)
        }
    }

    // TODO (CM): need to generate better error cases for this... if a
    // user inputs invalid input, the results can be cryptic:
    //
    // For instance, a template of "{{" give the error
    //
    //   ✗✗✗ Template "Unnamed template" line 1, col 3: invalid
    //   handlebars syntax.
    //
    // Not terribly useful, as there's no indication of what the
    // offending input is.
    //
    // We might want to pass more context to this render call (so
    // users can know which template was the offender)
    fn render<S>(template: &str, context: &S) -> Result<String>
        where S: Serialize
    {
        Ok(Handlebars::new().template_render(template, context)
                            .map_err(SyncFailure::new)
                            .map(|s| s.to_lowercase())?)
    }

    fn rendering_context(ident: &FullyQualifiedPackageIdent,
                         channel: &ChannelIdent)
                         -> impl Serialize {
        json!({
            "pkg_origin": ident.origin(),
            "pkg_name": ident.name(),
            "pkg_version": ident.version(),
            "pkg_release": ident.release(),
            "channel": channel,
        })
    }
}

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

    fn ident() -> FullyQualifiedPackageIdent { "core/foo/1.2.3/20200430153200".parse().unwrap() }

    fn context() -> impl Serialize {
        let ident = ident();
        let channel = ChannelIdent::default();
        Naming::rendering_context(&ident, &channel)
    }

    #[test]
    fn default_naming_policy() {
        let naming = Naming::default();
        let context = context();

        assert!(naming.latest_tag().is_none());
        assert!(naming.custom_tag(&context).unwrap().is_none());
        assert!(naming.version_tag(&context).is_none());
        assert!(naming.version_release_tag(&context).is_none());

        assert_eq!(naming.image_name(&context).unwrap(), "core/foo");
    }

    #[test]
    fn latest_tag() {
        let mut naming = Naming::default();
        naming.latest_tag = true;
        assert_eq!(naming.latest_tag().unwrap(), "latest");
    }

    #[test]
    fn version_tag() {
        let mut naming = Naming::default();
        naming.version_tag = true;

        let context = context();
        assert_eq!(naming.version_tag(&context).unwrap(), "1.2.3");
    }

    #[test]
    fn version_release_tag() {
        let mut naming = Naming::default();
        naming.version_release_tag = true;

        let context = context();
        assert_eq!(naming.version_release_tag(&context).unwrap(),
                   "1.2.3-20200430153200");
    }

    #[test]
    fn image_name_with_registry_url() {
        let mut naming = Naming::default();
        // TODO (CM): IMPLEMENTATION QUIRK
        // Registry type has no bearing on this! Fix it!
        naming.registry_url = Some(String::from("registry.mycompany.com:8080/v1"));

        let context = context();

        let name = naming.image_name(&context).unwrap();
        assert_eq!(name, "registry.mycompany.com:8080/v1/core/foo");
    }

    #[test]
    fn custom_image_names() {
        let context = context();

        // Template, Expected Result
        //
        // A "Some" expected result is something you expect to successfully
        // pass. A "None", on the other hand, is something you expect
        // to throw an error.
        let inputs = vec![

            // Valid inputs
            ("monkeys", Some("monkeys")),
            ("{{pkg_name}}", Some("foo")),
            ("{{pkg_origin}}-{{pkg_name}}", Some("core-foo")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}", Some("core-foo-1.2.3")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}",
             Some("core-foo-1.2.3-20200430153200")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}-{{channel}}",
             Some("core-foo-1.2.3-20200430153200-stable")),
            ("super-{{pkg_origin}}-wacky-{{pkg_name}}-funtime-{{pkg_version}}-container-{{pkg_release}}-party-{{channel}}-ohemgee",
             Some("super-core-wacky-foo-funtime-1.2.3-container-20200430153200-party-stable-ohemgee")),

            // Invalid inputs
            ("{{" , None),
            // ("{{not_a_valid_variable}}-{{pkg_name}}", None),
            // ("", None),
            // more examples of things that violate the tagging spec
        ];

        for (template, expected) in inputs {
            let mut naming = Naming::default();

            let template = String::from(template);
            naming.custom_image_name = Some(template.clone());
            let actual_name = naming.image_name(&context);

            if let Some(expected_name) = expected {
                // expected success
                assert!(actual_name.is_ok());
                let actual_name = actual_name.unwrap();
                assert_eq!(actual_name, expected_name,
                           "Expected template '{}' to generate image name '{}', but it generated \
                            '{}'",
                           template, expected_name, actual_name);
            } else {
                // expected failure
                assert!(actual_name.is_err(),
                        "Expected template '{}' to fail to generate an image name, but it \
                         generated '{}'",
                        template,
                        actual_name.unwrap());
            }
        }
    }

    // TODO (CM): there is a bunch of duplication here :(

    #[test]
    fn custom_tag_names() {
        let context = context();

        // Template, Expected Result
        //
        // A "Some" expected result is something you expect to successfully
        // pass. A "None", on the other hand, is something you expect
        // to throw an error.
        let inputs = vec![

            // Valid inputs
            ("monkeys", Some("monkeys")),
            ("{{pkg_name}}", Some("foo")),
            ("{{pkg_origin}}-{{pkg_name}}", Some("core-foo")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}", Some("core-foo-1.2.3")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}",
             Some("core-foo-1.2.3-20200430153200")),
            ("{{pkg_origin}}-{{pkg_name}}-{{pkg_version}}-{{pkg_release}}-{{channel}}",
             Some("core-foo-1.2.3-20200430153200-stable")),
            ("super-{{pkg_origin}}-wacky-{{pkg_name}}-funtime-{{pkg_version}}-container-{{pkg_release}}-party-{{channel}}-ohemgee",
             Some("super-core-wacky-foo-funtime-1.2.3-container-20200430153200-party-stable-ohemgee")),

            // Invalid inputs
            ("{{" , None),
            // ("{{not_a_valid_variable}}-{{pkg_name}}", None),
            // ("", None),
            // more examples of things that violate the tagging spec
        ];

        for (template, expected) in inputs {
            let mut naming = Naming::default();

            let template = String::from(template);
            naming.custom_tag = Some(template.clone());
            let actual_tag = naming.custom_tag(&context);

            if let Some(expected_tag) = expected {
                // expected success
                assert!(actual_tag.is_ok());
                let actual_tag = actual_tag.unwrap().unwrap(); // Result<Option<String>>
                assert_eq!(actual_tag, expected_tag,
                           "Expected template '{}' to generate tag '{}', but it generated '{}'",
                           template, expected_tag, actual_tag);
            } else {
                // expected failure
                assert!(actual_tag.is_err(),
                        "Expected template '{}' to fail to generate a tag, but it generated '{}'",
                        template,
                        actual_tag.unwrap().unwrap());
            }
        }
    }

    #[test]
    fn default_image_identifiers() {
        let naming = Naming::default();
        let ident = ident();
        let channel = ChannelIdent::default();

        let (name, tags) = naming.image_identifiers(&ident, &channel).unwrap();

        assert_eq!(name, "core/foo");
        assert!(tags.is_empty());
    }

    #[test]
    fn all_the_image_identifiers() {
        let naming = Naming { custom_image_name:   Some(String::from("my-nifty/{{pkg_name}}")),
                              latest_tag:          true,
                              version_tag:         true,
                              version_release_tag: true,
                              custom_tag:          Some(String::from("new-hotness")),
                              registry_url:        Some(String::from("registry.mycompany.com:\
                                                                      8080/v1")),
                              registry_type:       RegistryType::Docker, };

        let ident = ident();
        let channel = ChannelIdent::default();

        let (name, tags) = naming.image_identifiers(&ident, &channel).unwrap();

        assert_eq!(name, "registry.mycompany.com:8080/v1/my-nifty/foo");
        assert_eq!(tags,
                   ["latest", "1.2.3", "1.2.3-20200430153200", "new-hotness"]);
    }
}
<|endoftext|>


<commit_before>extern crate foxbox_taxonomy;
extern crate foxbox_thinkerbell;

extern crate transformable_channels;

use foxbox_thinkerbell::fake_env::*;
use foxbox_thinkerbell::parse::*;
use foxbox_thinkerbell::run::*;
use foxbox_thinkerbell::ast::*;

use foxbox_taxonomy::util::Id;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ OnOff, Range, Value };

use std::marker::PhantomData;
use std::thread;
use std::collections::{ HashMap, HashSet };

use transformable_channels::mpsc::*;

#[derive(Debug)]
enum Event {
    Env(FakeEnvEvent),
    Run(ExecutionEvent),
}

#[test]
fn test_compile() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            println!("LOG: {:?}", msg)
        }
    });

    println!("* Attempting to parse an run an empty script will raise an error.");
    let script = Script::parse("{\"rules\": []}").unwrap();
    match exec.start(env, script, tx_run) {
        Err(Error::CompileError(CompileError::SourceError(SourceError::NoRule))) => {},
        other => panic!("Unexpected result {:?}", other)
    }

    println!("//FIXME: Attempting to parse a script with an empty condition will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty statement will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty source will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty destination will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a match will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a send will raise an error.");

    println!("");
}

#[test]
fn test_run() {
    let (tx, rx) : (_, Receiver<Event>) = channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));
    let (tx_done, rx_done) = channel();

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            if let Event::Env(FakeEnvEvent::Done) = msg {
                tx_done.send(()).unwrap();
            } else {
                println!("LOG: {:?}", msg);
            }
        }
    });

    let script = Script {
        rules: vec![
            Rule {
                conditions: vec![
                    Match {
                        source: vec![
                            GetterSelector::new()
                        ],
                        kind: ChannelKind::OnOff,
                        range: Range::Eq(Value::OnOff(OnOff::On)),
                        duration: None,
                        phantom: PhantomData
                    }
                ],
                execute: vec![
                    Statement {
                        destination: vec![
                            SetterSelector::new()
                        ],
                        value: Value::OnOff(OnOff::Off),
                        kind: ChannelKind::OnOff,
                        phantom: PhantomData,
                    }
                ],
                phantom: PhantomData
            }
        ],
        phantom: PhantomData,
    };

    let adapter_id_1 = Id::<AdapterId>::new("Adapter 1");
    let service_id_1 = Id::<ServiceId>::new("Service 1");
    let getter_id_1 = Id::<Getter>::new("Getter 1");
    let setter_id_1 = Id::<Setter>::new("Setter 1");

    println!("* We can start executing a trivial rule.");
    exec.start(env.clone(), script, tx_run).unwrap();

    println!("* Changing the structure of the network doesn't break the rule.");
    env.execute(Instruction::AddAdapters(vec![adapter_id_1.to_string()]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddServices(vec![
        Service {
            id: service_id_1.clone(),
            adapter: adapter_id_1.clone(),
            getters: HashMap::new(),
            setters: HashMap::new(),
            tags: HashSet::new(),
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddGetters(vec![
        Channel {
            id: getter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            tags: HashSet::new(),
            last_seen: None,
            mechanism: Getter {
                watch: true,
                poll: None,
                updated: None,
                trigger: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddSetters(vec![
        Channel {
            id: setter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            last_seen: None,
            tags: HashSet::new(),
            mechanism: Setter {
                push: None,
                updated: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    println!("FIXME: Injecting the expected value triggers the send.");

    println!("");
}<commit_msg>[fxbox/thinkerbell] test_rules: First meaningful test passes<commit_after>extern crate foxbox_taxonomy;
extern crate foxbox_thinkerbell;

extern crate transformable_channels;

use foxbox_thinkerbell::fake_env::*;
use foxbox_thinkerbell::parse::*;
use foxbox_thinkerbell::run::*;
use foxbox_thinkerbell::ast::*;

use foxbox_taxonomy::util::Id;
use foxbox_taxonomy::selector::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ OnOff, Range, Value };

use std::marker::PhantomData;
use std::thread;
use std::collections::{ HashMap, HashSet };

use transformable_channels::mpsc::*;

#[derive(Debug)]
enum Event {
    Env(FakeEnvEvent),
    Run(ExecutionEvent),
}

#[test]
fn test_compile() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            println!("LOG: {:?}", msg)
        }
    });

    println!("* Attempting to parse an run an empty script will raise an error.");
    let script = Script::parse("{\"rules\": []}").unwrap();
    match exec.start(env, script, tx_run) {
        Err(Error::CompileError(CompileError::SourceError(SourceError::NoRule))) => {},
        other => panic!("Unexpected result {:?}", other)
    }

    println!("//FIXME: Attempting to parse a script with an empty condition will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty statement will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty source will raise an error.");
    println!("//FIXME: Attempting to parse a script with an empty destination will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a match will raise an error.");
    println!("//FIXME: Attempting to parse a script with a type error in a send will raise an error.");

    println!("");
}

fn race<T>(receivers: &[Receiver<T>]) -> T {
    use std::time::Duration;
    loop {
        for rx in receivers {
            if let Ok(result) = rx.try_recv() {
                return result
            }
        }
        thread::sleep(std::time::Duration::new(1, 0));
    }
}

#[test]
fn test_run() {
    let (tx, rx) : (_, Receiver<Event>)= channel();

    let tx_env = Box::new(tx.map(|event| Event::Env(event)));
    let tx_run = tx.map(|event| Event::Run(event));
    let (tx_done, rx_done) = channel();
    let (tx_send, rx_send) = channel();

    let env = FakeEnv::new(tx_env);
    let mut exec = Execution::<FakeEnv>::new();

    thread::spawn(move || {
        for msg in rx {
            if let Event::Env(FakeEnvEvent::Done) = msg {
                tx_done.send(()).unwrap();
            } else if let Event::Env(FakeEnvEvent::Send { id, value }) = msg {
                tx_send.send((id, value)).unwrap();
            } else {
                println!("LOG: {:?}", msg);
            }
        }
    });

    let script_1 = Script {
        rules: vec![
            Rule {
                conditions: vec![
                    Match {
                        source: vec![
                            GetterSelector::new()
                        ],
                        kind: ChannelKind::OnOff,
                        range: Range::Eq(Value::OnOff(OnOff::On)),
                        duration: None,
                        phantom: PhantomData
                    }
                ],
                execute: vec![
                    Statement {
                        destination: vec![
                            SetterSelector::new()
                        ],
                        value: Value::OnOff(OnOff::Off),
                        kind: ChannelKind::OnOff,
                        phantom: PhantomData,
                    }
                ],
                phantom: PhantomData
            }
        ],
        phantom: PhantomData,
    };

    let adapter_id_1 = Id::<AdapterId>::new("Adapter 1");
    let service_id_1 = Id::<ServiceId>::new("Service 1");
    let getter_id_1 = Id::<Getter>::new("Getter 1");
    let setter_id_1 = Id::<Setter>::new("Setter 1");

    println!("* We can start executing a trivial rule.");
    exec.start(env.clone(), script_1, tx_run).unwrap();

    println!("* Changing the structure of the network doesn't break the rule.");
    env.execute(Instruction::AddAdapters(vec![adapter_id_1.to_string()]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddServices(vec![
        Service {
            id: service_id_1.clone(),
            adapter: adapter_id_1.clone(),
            getters: HashMap::new(),
            setters: HashMap::new(),
            tags: HashSet::new(),
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddGetters(vec![
        Channel {
            id: getter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            tags: HashSet::new(),
            last_seen: None,
            mechanism: Getter {
                watch: true,
                poll: None,
                updated: None,
                trigger: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    env.execute(Instruction::AddSetters(vec![
        Channel {
            id: setter_id_1.clone(),
            adapter: adapter_id_1.clone(),
            service: service_id_1.clone(),
            last_seen: None,
            tags: HashSet::new(),
            mechanism: Setter {
                push: None,
                updated: None,
                kind: ChannelKind::OnOff,
            }
        }
    ]));
    rx_done.recv().unwrap();

    println!("* Injecting the expected value triggers the send.");
    env.execute(Instruction::InjectGetterValues(vec![
        (getter_id_1.clone(), Value::OnOff(OnOff::On))
    ]));

    rx_done.recv().unwrap();
    let (id, value) = rx_send.recv().unwrap();
    assert_eq!(id, setter_id_1);
    assert_eq!(value, Value::OnOff(OnOff::Off));

    println!("");
}<|endoftext|>


<commit_before>use crate::error::{Error, Result};
use std::env;
use std::fs;
use std::os;
use std::path::{Path, PathBuf};

fn out_dir() -> Result<PathBuf> {
    env::var_os("OUT_DIR")
        .map(PathBuf::from)
        .ok_or(Error::MissingOutDir)
}

pub(crate) fn cc_build() -> cc::Build {
    try_cc_build().unwrap_or_default()
}

fn try_cc_build() -> Result<cc::Build> {
    let target_dir = target_dir()?;

    let mut build = cc::Build::new();
    build.include(target_dir.join("cxxbridge"));
    build.include(target_dir.parent().unwrap());
    Ok(build)
}

// Symlink the header file into a predictable place. The header generated from
// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.h.
pub(crate) fn symlink_header(path: &Path, original: &Path) {
    let _ = try_symlink_header(path, original);
}

fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
    let suffix = relative_to_parent_of_target_dir(original)?;
    let dst = target_dir()?.join("cxxbridge").join(suffix);

    fs::create_dir_all(dst.parent().unwrap())?;
    #[cfg(unix)]
    os::unix::fs::symlink(path, dst)?;
    #[cfg(windows)]
    os::windows::fs::symlink_file(path, dst)?;

    Ok(())
}

fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
    let target_dir = target_dir()?;
    let mut outer = target_dir.parent().unwrap();
    let original = original.canonicalize()?;
    loop {
        if let Ok(suffix) = original.strip_prefix(outer) {
            return Ok(suffix.to_owned());
        }
        match outer.parent() {
            Some(parent) => outer = parent,
            None => return Ok(original.components().skip(1).collect()),
        }
    }
}

pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
    let mut file_name = path.file_name().unwrap().to_owned();
    file_name.push(ext);

    let out_dir = out_dir()?;
    let rel = relative_to_parent_of_target_dir(path)?;
    Ok(out_dir.join(rel).with_file_name(file_name))
}

fn target_dir() -> Result<PathBuf> {
    let mut dir = out_dir()?.canonicalize()?;
    loop {
        if dir.ends_with("target") {
            return Ok(dir);
        }
        if !dir.pop() {
            return Err(Error::TargetDir);
        }
    }
}
<commit_msg>Remove any existing link before writing symlink<commit_after>use crate::error::{Error, Result};
use std::env;
use std::fs;
use std::os;
use std::path::{Path, PathBuf};

fn out_dir() -> Result<PathBuf> {
    env::var_os("OUT_DIR")
        .map(PathBuf::from)
        .ok_or(Error::MissingOutDir)
}

pub(crate) fn cc_build() -> cc::Build {
    try_cc_build().unwrap_or_default()
}

fn try_cc_build() -> Result<cc::Build> {
    let target_dir = target_dir()?;

    let mut build = cc::Build::new();
    build.include(target_dir.join("cxxbridge"));
    build.include(target_dir.parent().unwrap());
    Ok(build)
}

// Symlink the header file into a predictable place. The header generated from
// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.h.
pub(crate) fn symlink_header(path: &Path, original: &Path) {
    let _ = try_symlink_header(path, original);
}

fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
    let suffix = relative_to_parent_of_target_dir(original)?;
    let ref dst = target_dir()?.join("cxxbridge").join(suffix);

    fs::create_dir_all(dst.parent().unwrap())?;
    let _ = fs::remove_file(dst);
    #[cfg(unix)]
    os::unix::fs::symlink(path, dst)?;
    #[cfg(windows)]
    os::windows::fs::symlink_file(path, dst)?;

    Ok(())
}

fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
    let target_dir = target_dir()?;
    let mut outer = target_dir.parent().unwrap();
    let original = original.canonicalize()?;
    loop {
        if let Ok(suffix) = original.strip_prefix(outer) {
            return Ok(suffix.to_owned());
        }
        match outer.parent() {
            Some(parent) => outer = parent,
            None => return Ok(original.components().skip(1).collect()),
        }
    }
}

pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
    let mut file_name = path.file_name().unwrap().to_owned();
    file_name.push(ext);

    let out_dir = out_dir()?;
    let rel = relative_to_parent_of_target_dir(path)?;
    Ok(out_dir.join(rel).with_file_name(file_name))
}

fn target_dir() -> Result<PathBuf> {
    let mut dir = out_dir()?.canonicalize()?;
    loop {
        if dir.ends_with("target") {
            return Ok(dir);
        }
        if !dir.pop() {
            return Err(Error::TargetDir);
        }
    }
}
<|endoftext|>


<commit_before>use cgmath::{Point, Point3, Vector, Vector3};
use cgmath::Aabb3;
use std::cmp::{min, max, partial_min, partial_max};
use std::num::Int;
use std::sync::Mutex;

use common::block_position::BlockPosition;
use common::entity::EntityId;
use common::id_allocator::IdAllocator;
use common::lod::LODIndex;
use common::stopwatch::TimerSet;
use common::terrain_block::{TerrainBlock, BLOCK_WIDTH, LOD_QUALITY, tri};

use terrain::heightmap::HeightMap;
use terrain::terrain::{Fracu8, Fraci8, Voxel, VoxelVertex, VoxelNormal, Edge};
use voxel_tree;
use voxel_tree::{VoxelBounds, VoxelTree};

fn generate_voxel<FieldContains, GetNormal>(
  timers: &TimerSet,
  field_contains: &mut FieldContains,
  get_normal: &mut GetNormal,
  voxel: VoxelBounds,
) -> Option<Voxel>
  where
    FieldContains: FnMut(f32, f32, f32) -> bool,
    GetNormal: FnMut(f32, f32, f32) -> Vector3<f32>,
{
  timers.time("generate_voxel", || {
    let size = voxel.size();
    let (x1, y1, z1) = (voxel.x as f32 * size, voxel.y as f32 * size, voxel.z as f32 * size);
    let delta = size;
    let (x2, y2, z2) = (x1 + delta, y1 + delta, z1 + delta);
    // corners[x][y][z]
    let corners = [
      [
        [ field_contains(x1, y1, z1), field_contains(x1, y1, z2) ],
        [ field_contains(x1, y2, z1), field_contains(x1, y2, z2) ],
      ],
      [
        [ field_contains(x2, y1, z1), field_contains(x2, y1, z2) ],
        [ field_contains(x2, y2, z1), field_contains(x2, y2, z2) ],
      ],
    ];

    let x_edges;
    let y_edges;
    let z_edges;
    let mut any_crossed = false;

    {
      let mut edge = |x1:usize, y1:usize, z1:usize, x2:usize, y2:usize, z2:usize| {
        let is_crossed = corners[x1][y1][z1] != corners[x2][y2][z2];
        any_crossed = any_crossed || is_crossed;
        Edge {
          is_crossed: is_crossed,
          direction: corners[x2][y2][z2],
        }
      };

      x_edges = [
        [ edge(0,0,0, 1,0,0), edge(0,0,1, 1,0,1) ],
        [ edge(0,1,0, 1,1,0), edge(0,1,1, 1,1,1) ],
      ];
      y_edges = [
        [ edge(0,0,0, 0,1,0), edge(1,0,0, 1,1,0) ],
        [ edge(0,0,1, 0,1,1), edge(1,0,1, 1,1,1) ],
      ];
      z_edges = [
        [ edge(0,0,0, 0,0,1), edge(1,0,0, 1,0,1) ],
        [ edge(0,1,0, 0,1,1), edge(1,1,0, 1,1,1) ],
      ];
    }

    if !any_crossed {
      return None
    }

    let mut vertex: Vector3<u32> = Vector3::new(0, 0, 0);
    let mut n = 0;
    for (&x, corners) in [0, 0xFF].iter().zip(corners.iter()) {
    for (&y, corners) in [0, 0xFF].iter().zip(corners.iter()) {
    for (&z, &corner) in [0, 0xFF].iter().zip(corners.iter()) {
      if corner {
        vertex.add_self_v(&Vector3::new(x, y, z));
        n += 1;
      }
    }}}

    {
      // Sample in extra areas to help weight the vertex to the appropriate place.
      let mut sample_extra = |lg_s: u8| {
        let fs = 1.0 / ((1 << lg_s) as f32);
        let mfs = 1.0 - fs;
        let s = 0x100 >> lg_s;
        let ms = 0x100 - s;
        for &(wx, x) in
          [((voxel.x as f32 + fs) * size, s), ((voxel.x as f32 + mfs) * size, ms)].iter() {
        for &(wy, y) in
          [((voxel.y as f32 + fs) * size, s), ((voxel.y as f32 + mfs) * size, ms)].iter() {
        for &(wz, z) in
          [((voxel.z as f32 + fs) * size, s), ((voxel.z as f32 + mfs) * size, ms)].iter() {
          if field_contains(wx, wy, wz) {
            vertex.add_self_v(&Vector3::new(x, y, z).mul_s(lg_s as u32));
            n += lg_s as u32;
          }
        }}}
      };

      sample_extra(2);
    }

    let vertex = vertex.div_s(n);
    let vertex =
      VoxelVertex {
        x: Fracu8::of(vertex.x as u8),
        y: Fracu8::of(vertex.y as u8),
        z: Fracu8::of(vertex.z as u8),
      };

    let normal;
    {
      // Okay, this is silly to have right after we construct the vertex.
      let vertex = vertex.to_world_vertex(voxel);
      normal = get_normal(vertex.x, vertex.y, vertex.z).mul_s(127.0);
    }

    // Okay, so we scale the normal by 127, and use 127 to represent 1.0.
    // Then we store it in a `Fraci8`, which scales by 128 and represents a
    // fraction in [0,1). That seems wrong, but this is normal data, so scaling
    // doesn't matter. Sketch factor is over 9000, but it's not wrong.

    let normal = Vector3::new(normal.x as i32, normal.y as i32, normal.z as i32);
    let normal =
      Vector3::new(
        max(-127, min(127, normal.x)) as i8,
        max(-127, min(127, normal.y)) as i8,
        max(-127, min(127, normal.z)) as i8,
      );

    let normal =
      VoxelNormal {
        x: Fraci8::of(normal.x),
        y: Fraci8::of(normal.y),
        z: Fraci8::of(normal.z),
      };

    Some(Voxel {
      vertex: vertex,
      normal: normal,
      x_edges: x_edges,
      y_edges: y_edges,
      z_edges: z_edges,
    })
  })
}

pub fn generate_block(
  timers: &TimerSet,
  id_allocator: &Mutex<IdAllocator<EntityId>>,
  heightmap: &HeightMap,
  voxels: &mut VoxelTree<Voxel>,
  position: &BlockPosition,
  lod_index: LODIndex,
) -> TerrainBlock {
  timers.time("update.generate_block", || {
    let mut block = TerrainBlock::empty();

    let position = position.to_world_position();
    let iposition = Point3::new(position.x as i32, position.y as i32, position.z as i32);

    let lateral_samples = LOD_QUALITY[lod_index.0 as usize] as i32;
    assert!(
      BLOCK_WIDTH % lateral_samples == 0 || lateral_samples % BLOCK_WIDTH == 0,
      "Block width doesn't sample cleanly."
    );

    let lg_block_width = Int::trailing_zeros(BLOCK_WIDTH);
    assert!(1 << lg_block_width == BLOCK_WIDTH, "BLOCK_WIDTH should be an exponent of 2");

    let lg_lateral_samples = Int::trailing_zeros(lateral_samples);
    assert!(
      1 << lg_lateral_samples == lateral_samples,
      "lateral_samples should be an exponent of 2");

    let lg_size: i16 = lg_block_width as i16 - lg_lateral_samples as i16;
    assert!(lg_size.abs() < 16, "2^{} is too a huge voxel.", lg_size);

    {
      let mut add_poly =
        |v1: Point3<f32>, n1, v2: Point3<f32>, n2, center: Point3<f32>, center_normal| {
          let id = id_allocator.lock().unwrap().allocate();

          let minx = partial_min(v1.x, v2.x);
          let minx = minx.and_then(|m| partial_min(m, center.x));
          let minx = minx.unwrap();

          let maxx = partial_max(v1.x, v2.x);
          let maxx = maxx.and_then(|m| partial_max(m, center.x));
          let maxx = maxx.unwrap();

          let miny = partial_min(v1.y, v2.y);
          let miny = miny.and_then(|m| partial_min(m, center.y));
          let miny = miny.unwrap();

          let maxy = partial_max(v1.y, v2.y);
          let maxy = maxy.and_then(|m| partial_max(m, center.y));
          let maxy = maxy.unwrap();

          let minz = partial_min(v1.z, v2.z);
          let minz = minz.and_then(|m| partial_min(m, center.z));
          let minz = minz.unwrap();

          let maxz = partial_max(v1.z, v2.z);
          let maxz = maxz.and_then(|m| partial_max(m, center.z));
          let maxz = maxz.unwrap();

          block.vertex_coordinates.push(tri(v1, v2, center));
          block.normals.push(tri(n1, n2, center_normal));
          block.ids.push(id);

          block.bounds.push((
            id,
            Aabb3::new(
              // TODO: Remove this - 1.0. It's a temporary hack until voxel collisions work.
              Point3::new(minx, miny - 1.0, minz),
              Point3::new(maxx, maxy, maxz),
            ),
          ));
        };

      let mut field_contains = |x, y, z| {
        heightmap.density_at(x, y, z) - y/64.0 >= 0.0
      };

      let mut get_normal = |x, y, z| {
        heightmap.normal_at(x, y, z, 0.01)
      };

      macro_rules! get_voxel(($bounds:expr) => {{
        let branch = voxels.get_mut($bounds);
        match branch {
          &mut voxel_tree::TreeBody::Leaf(v) => Some(v),
          &mut voxel_tree::TreeBody::Empty => {
            // TODO: Add a "yes this is empty I checked" flag so we don't regen every time.
            generate_voxel(timers, &mut field_contains, &mut get_normal, $bounds).map(|v| {
              *branch = voxel_tree::TreeBody::Leaf(v);
              v
            })
          },
          &mut voxel_tree::TreeBody::Branch(_) => {
            // Overwrite existing for now.
            // TODO: Don't do ^that.
            generate_voxel(timers, &mut field_contains, &mut get_normal, $bounds).map(|v| {
              *branch = voxel_tree::TreeBody::Leaf(v);
              v
            })
          },
        }
      }});

      macro_rules! get_vertex(($v:expr) => {{
        let bounds = VoxelBounds::new($v.x, $v.y, $v.z, lg_size);
        let voxel =
          get_voxel!(bounds)
          .unwrap_or_else(|| panic!("Couldn't find edge neighbor voxel"));
        (voxel.vertex.to_world_vertex(bounds), voxel.normal.to_world_normal())
      }});

      macro_rules! extract((
        $edges:ident,
        $facing:expr,
        $d1:expr,
        $d2:expr,
      ) => (
          for x in 0..lateral_samples {
          for y in 0..lateral_samples {
          for z in 0..lateral_samples {
            let w;
            {
              let iposition =
                if lg_size >= 0 {
                  let mask = (1 << lg_size) - 1;
                  assert!(
                    (iposition.x|iposition.y|iposition.z) & mask == 0,
                    "Block position should be a multiple of voxel sizes."
                  );
                  Point3::new(
                    iposition.x >> lg_size,
                    iposition.y >> lg_size,
                    iposition.z >> lg_size,
                  )
                } else {
                  let lg_size = -lg_size;
                  Point3::new(
                    iposition.x << lg_size,
                    iposition.y << lg_size,
                    iposition.z << lg_size,
                  )
                };
              w = iposition.add_v(&Vector3::new(x, y, z));
            }
            let bounds = VoxelBounds::new(w.x, w.y, w.z, lg_size);
            let voxel;
            match get_voxel!(bounds) {
              None => continue,
              Some(v) => voxel = v,
            }

            let edge = voxel.$edges[0][0];
            if !edge.is_crossed {
              continue
            }

            // Make a quad out of the vertices from the 4 voxels adjacent to this edge.
            // We know they have vertices in them because if the surface crosses an edge,
            // it must cross that edge's neighbors.

            let (v1, n1) = get_vertex!(w.add_v(&$d1).add_v(&$d2));
            let (v2, n2) = get_vertex!(w.add_v(&$d1));
            let v3 = voxel.vertex.to_world_vertex(bounds);
            let n3 = voxel.normal.to_world_normal();
            let (v4, n4) = get_vertex!(w.add_v(&$d2));

            // Put a vertex at the average of the vertices.
            let center =
              v1.add_v(&v2.to_vec()).add_v(&v3.to_vec()).add_v(&v4.to_vec()).div_s(4.0);
            let center_normal = n1.add_v(&n2).add_v(&n3).add_v(&n4).div_s(4.0);

            let mut add_poly = |v1, n1, v2, n2| add_poly(v1, n1, v2, n2, center, center_normal);

            if edge.direction {
              // The polys are visible from negative infinity.
              add_poly(v1, n1, v2, n2);
              add_poly(v2, n2, v3, n3);
              add_poly(v3, n3, v4, n4);
              add_poly(v4, n4, v1, n1);
            } else {
              // The polys are visible from positive infinity.
              add_poly(v1, n1, v4, n4);
              add_poly(v4, n4, v3, n3);
              add_poly(v3, n3, v2, n2);
              add_poly(v2, n2, v1, n1);
            }
          }}}
        )
      );

      extract!(
        x_edges, 0,
        Vector3::new(0, -1, 0),
        Vector3::new(0, 0, -1),
      );

      extract!(
        y_edges, 1,
        Vector3::new(0, 0, -1),
        Vector3::new(-1, 0, 0),
      );

      extract!(
        z_edges, 2,
        Vector3::new(-1, 0, 0),
        Vector3::new(0, -1, 0),
      );
    }

    block
  })
}
<commit_msg>Cleanups<commit_after>use cgmath::{Point, Point3, Vector, Vector3};
use cgmath::Aabb3;
use std::cmp::{min, max, partial_min, partial_max};
use std::num::Int;
use std::sync::Mutex;

use common::block_position::BlockPosition;
use common::entity::EntityId;
use common::id_allocator::IdAllocator;
use common::lod::LODIndex;
use common::stopwatch::TimerSet;
use common::terrain_block::{TerrainBlock, BLOCK_WIDTH, LOD_QUALITY, tri};

use terrain::heightmap::HeightMap;
use terrain::terrain::{Fracu8, Fraci8, Voxel, VoxelVertex, VoxelNormal, Edge};
use voxel_tree;
use voxel_tree::{VoxelBounds, VoxelTree};

fn generate_voxel<FieldContains, GetNormal>(
  timers: &TimerSet,
  field_contains: &mut FieldContains,
  get_normal: &mut GetNormal,
  voxel: VoxelBounds,
) -> Option<Voxel>
  where
    FieldContains: FnMut(f32, f32, f32) -> bool,
    GetNormal: FnMut(f32, f32, f32) -> Vector3<f32>,
{
  timers.time("generate_voxel", || {
    let size = voxel.size();
    let (x1, y1, z1) = (voxel.x as f32 * size, voxel.y as f32 * size, voxel.z as f32 * size);
    let delta = size;
    let (x2, y2, z2) = (x1 + delta, y1 + delta, z1 + delta);
    // corners[x][y][z]
    let corners = [
      [
        [ field_contains(x1, y1, z1), field_contains(x1, y1, z2) ],
        [ field_contains(x1, y2, z1), field_contains(x1, y2, z2) ],
      ],
      [
        [ field_contains(x2, y1, z1), field_contains(x2, y1, z2) ],
        [ field_contains(x2, y2, z1), field_contains(x2, y2, z2) ],
      ],
    ];

    let x_edges;
    let y_edges;
    let z_edges;
    let mut any_crossed = false;

    {
      let mut edge = |x1:usize, y1:usize, z1:usize, x2:usize, y2:usize, z2:usize| {
        let is_crossed = corners[x1][y1][z1] != corners[x2][y2][z2];
        any_crossed = any_crossed || is_crossed;
        Edge {
          is_crossed: is_crossed,
          direction: corners[x2][y2][z2],
        }
      };

      x_edges = [
        [ edge(0,0,0, 1,0,0), edge(0,0,1, 1,0,1) ],
        [ edge(0,1,0, 1,1,0), edge(0,1,1, 1,1,1) ],
      ];
      y_edges = [
        [ edge(0,0,0, 0,1,0), edge(0,0,1, 0,1,1) ],
        [ edge(1,0,0, 1,1,0), edge(1,0,1, 1,1,1) ],
      ];
      z_edges = [
        [ edge(0,0,0, 0,0,1), edge(0,1,0, 0,1,1) ],
        [ edge(1,0,0, 1,0,1), edge(1,1,0, 1,1,1) ],
      ];
    }

    if !any_crossed {
      return None
    }

    let mut vertex: Vector3<u32> = Vector3::new(0, 0, 0);
    let mut n = 0;
    for (&x, corners) in [0, 0xFF].iter().zip(corners.iter()) {
    for (&y, corners) in [0, 0xFF].iter().zip(corners.iter()) {
    for (&z, &corner) in [0, 0xFF].iter().zip(corners.iter()) {
      if corner {
        vertex.add_self_v(&Vector3::new(x, y, z));
        n += 1;
      }
    }}}

    {
      // Sample in extra areas to help weight the vertex to the appropriate place.
      let mut sample_extra = |lg_s: u8| {
        let fs = 1.0 / ((1 << lg_s) as f32);
        let mfs = 1.0 - fs;
        let s = 0x100 >> lg_s;
        let ms = 0x100 - s;
        for &(wx, x) in
          [((voxel.x as f32 + fs) * size, s), ((voxel.x as f32 + mfs) * size, ms)].iter() {
        for &(wy, y) in
          [((voxel.y as f32 + fs) * size, s), ((voxel.y as f32 + mfs) * size, ms)].iter() {
        for &(wz, z) in
          [((voxel.z as f32 + fs) * size, s), ((voxel.z as f32 + mfs) * size, ms)].iter() {
          if field_contains(wx, wy, wz) {
            vertex.add_self_v(&Vector3::new(x, y, z).mul_s(lg_s as u32));
            n += lg_s as u32;
          }
        }}}
      };

      sample_extra(2);
    }

    let vertex = vertex.div_s(n);
    let vertex =
      VoxelVertex {
        x: Fracu8::of(vertex.x as u8),
        y: Fracu8::of(vertex.y as u8),
        z: Fracu8::of(vertex.z as u8),
      };

    let normal;
    {
      // Okay, this is silly to have right after we construct the vertex.
      let vertex = vertex.to_world_vertex(voxel);
      normal = get_normal(vertex.x, vertex.y, vertex.z).mul_s(127.0);
    }

    // Okay, so we scale the normal by 127, and use 127 to represent 1.0.
    // Then we store it in a `Fraci8`, which scales by 128 and represents a
    // fraction in [0,1). That seems wrong, but this is normal data, so scaling
    // doesn't matter. Sketch factor is over 9000, but it's not wrong.

    let normal = Vector3::new(normal.x as i32, normal.y as i32, normal.z as i32);
    let normal =
      Vector3::new(
        max(-127, min(127, normal.x)) as i8,
        max(-127, min(127, normal.y)) as i8,
        max(-127, min(127, normal.z)) as i8,
      );

    let normal =
      VoxelNormal {
        x: Fraci8::of(normal.x),
        y: Fraci8::of(normal.y),
        z: Fraci8::of(normal.z),
      };

    Some(Voxel {
      vertex: vertex,
      normal: normal,
      x_edges: x_edges,
      y_edges: y_edges,
      z_edges: z_edges,
    })
  })
}

pub fn generate_block(
  timers: &TimerSet,
  id_allocator: &Mutex<IdAllocator<EntityId>>,
  heightmap: &HeightMap,
  voxels: &mut VoxelTree<Voxel>,
  position: &BlockPosition,
  lod_index: LODIndex,
) -> TerrainBlock {
  timers.time("update.generate_block", || {
    let mut block = TerrainBlock::empty();

    let position = position.to_world_position();
    let iposition = Point3::new(position.x as i32, position.y as i32, position.z as i32);

    let lateral_samples = LOD_QUALITY[lod_index.0 as usize] as i32;
    assert!(
      BLOCK_WIDTH % lateral_samples == 0 || lateral_samples % BLOCK_WIDTH == 0,
      "Block width doesn't sample cleanly."
    );

    let lg_block_width = Int::trailing_zeros(BLOCK_WIDTH);
    assert!(1 << lg_block_width == BLOCK_WIDTH, "BLOCK_WIDTH should be an exponent of 2");

    let lg_lateral_samples = Int::trailing_zeros(lateral_samples);
    assert!(
      1 << lg_lateral_samples == lateral_samples,
      "lateral_samples should be an exponent of 2");

    let lg_size: i16 = lg_block_width as i16 - lg_lateral_samples as i16;
    assert!(lg_size.abs() < 16, "2^{} is too a huge voxel.", lg_size);

    {
      let mut add_poly =
        |v1: Point3<f32>, n1, v2: Point3<f32>, n2, center: Point3<f32>, center_normal| {
          let id = id_allocator.lock().unwrap().allocate();

          let minx = partial_min(v1.x, v2.x);
          let minx = minx.and_then(|m| partial_min(m, center.x));
          let minx = minx.unwrap();

          let maxx = partial_max(v1.x, v2.x);
          let maxx = maxx.and_then(|m| partial_max(m, center.x));
          let maxx = maxx.unwrap();

          let miny = partial_min(v1.y, v2.y);
          let miny = miny.and_then(|m| partial_min(m, center.y));
          let miny = miny.unwrap();

          let maxy = partial_max(v1.y, v2.y);
          let maxy = maxy.and_then(|m| partial_max(m, center.y));
          let maxy = maxy.unwrap();

          let minz = partial_min(v1.z, v2.z);
          let minz = minz.and_then(|m| partial_min(m, center.z));
          let minz = minz.unwrap();

          let maxz = partial_max(v1.z, v2.z);
          let maxz = maxz.and_then(|m| partial_max(m, center.z));
          let maxz = maxz.unwrap();

          block.vertex_coordinates.push(tri(v1, v2, center));
          block.normals.push(tri(n1, n2, center_normal));
          block.ids.push(id);

          block.bounds.push((
            id,
            Aabb3::new(
              // TODO: Remove this - 1.0. It's a temporary hack until voxel collisions work.
              Point3::new(minx, miny - 1.0, minz),
              Point3::new(maxx, maxy, maxz),
            ),
          ));
        };

      let mut field_contains = |x, y, z| {
        heightmap.density_at(x, y, z) - y/64.0 >= 0.0
      };

      let mut get_normal = |x, y, z| {
        heightmap.normal_at(x, y, z, 0.01)
      };

      macro_rules! get_voxel(($bounds:expr) => {{
        let branch = voxels.get_mut($bounds);
        match branch {
          &mut voxel_tree::TreeBody::Leaf(v) => Some(v),
          &mut voxel_tree::TreeBody::Empty => {
            // TODO: Add a "yes this is empty I checked" flag so we don't regen every time.
            generate_voxel(timers, &mut field_contains, &mut get_normal, $bounds).map(|v| {
              *branch = voxel_tree::TreeBody::Leaf(v);
              v
            })
          },
          &mut voxel_tree::TreeBody::Branch(_) => {
            // Overwrite existing for now.
            // TODO: Don't do ^that.
            generate_voxel(timers, &mut field_contains, &mut get_normal, $bounds).map(|v| {
              *branch = voxel_tree::TreeBody::Leaf(v);
              v
            })
          },
        }
      }});

      macro_rules! get_vertex(($v:expr) => {{
        let bounds = VoxelBounds::new($v.x, $v.y, $v.z, lg_size);
        let voxel =
          get_voxel!(bounds)
          .unwrap_or_else(|| panic!("Couldn't find edge neighbor voxel"));
        (voxel.vertex.to_world_vertex(bounds), voxel.normal.to_world_normal())
      }});

      macro_rules! extract((
        $edges:ident,
        $d1:expr,
        $d2:expr,
      ) => (
        for x in 0..lateral_samples {
        for y in 0..lateral_samples {
        for z in 0..lateral_samples {
          let w;
          {
            let iposition =
              if lg_size >= 0 {
                let mask = (1 << lg_size) - 1;
                assert!(
                  (iposition.x|iposition.y|iposition.z) & mask == 0,
                  "Block position should be a multiple of voxel sizes."
                );
                Point3::new(
                  iposition.x >> lg_size,
                  iposition.y >> lg_size,
                  iposition.z >> lg_size,
                )
              } else {
                let lg_size = -lg_size;
                Point3::new(
                  iposition.x << lg_size,
                  iposition.y << lg_size,
                  iposition.z << lg_size,
                )
              };
            w = iposition.add_v(&Vector3::new(x, y, z));
          }
          let bounds = VoxelBounds::new(w.x, w.y, w.z, lg_size);
          let voxel;
          match get_voxel!(bounds) {
            None => continue,
            Some(v) => voxel = v,
          }

          let edge = voxel.$edges[0][0];
          if !edge.is_crossed {
            continue
          }

          // Make a quad out of the vertices from the 4 voxels adjacent to this edge.
          // We know they have vertices in them because if the surface crosses an edge,
          // it must cross that edge's neighbors.

          let (v1, n1) = get_vertex!(w.add_v(&$d1).add_v(&$d2));
          let (v2, n2) = get_vertex!(w.add_v(&$d1));
          let v3 = voxel.vertex.to_world_vertex(bounds);
          let n3 = voxel.normal.to_world_normal();
          let (v4, n4) = get_vertex!(w.add_v(&$d2));

          // Put a vertex at the average of the vertices.
          let center =
            v1.add_v(&v2.to_vec()).add_v(&v3.to_vec()).add_v(&v4.to_vec()).div_s(4.0);
          let center_normal = n1.add_v(&n2).add_v(&n3).add_v(&n4).div_s(4.0);

          let mut add_poly = |v1, n1, v2, n2| add_poly(v1, n1, v2, n2, center, center_normal);

          if edge.direction {
            // The polys are visible from negative infinity.
            add_poly(v1, n1, v2, n2);
            add_poly(v2, n2, v3, n3);
            add_poly(v3, n3, v4, n4);
            add_poly(v4, n4, v1, n1);
          } else {
            // The polys are visible from positive infinity.
            add_poly(v1, n1, v4, n4);
            add_poly(v4, n4, v3, n3);
            add_poly(v3, n3, v2, n2);
            add_poly(v2, n2, v1, n1);
          }
        }}}
        )
      );

      extract!(
        x_edges,
        Vector3::new(0, -1, 0),
        Vector3::new(0, 0, -1),
      );

      extract!(
        y_edges,
        Vector3::new(0, 0, -1),
        Vector3::new(-1, 0, 0),
      );

      extract!(
        z_edges,
        Vector3::new(-1, 0, 0),
        Vector3::new(0, -1, 0),
      );
    }

    block
  })
}
<|endoftext|>