<commit_before>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_span::edition::Edition;
use rustc_span::{hygiene, sym, symbol::kw, symbol::SymbolStr, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANICS,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    @future_incompatible = FutureIncompatibleInfo {
        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
        explain_reason: false,
    };
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic, symbol_str) = panic_call(cx, f);

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    // We don't use source_callsite(), because this `panic!(..)` might itself
    // be expanded from another macro, in which case we want to stop at that
    // expansion.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this usage of panic!() is deprecated; it will be a hard error in Rust 2021");
        l.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
        if !span.contains(arg_span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note(format!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol_str).as_str());
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                Applicability::MaybeIncorrect,
            );
            if panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    let (span, _, _) = panic_call(cx, f);

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan { start: open, end: open + 1 }),
        span.from_inner(InnerSpan { start: close, end: close + 1 }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, SymbolStr) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    let macro_symbol =
        if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
    (expn.call_site, panic_macro, macro_symbol.as_str())
}
<commit_msg>Detect fake spans in non_fmt_panic lint.<commit_after>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_span::edition::Edition;
use rustc_span::{hygiene, sym, symbol::kw, symbol::SymbolStr, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANICS,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    @future_incompatible = FutureIncompatibleInfo {
        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
        explain_reason: false,
    };
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic, symbol_str) = panic_call(cx, f);

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    // We don't use source_callsite(), because this `panic!(..)` might itself
    // be expanded from another macro, in which case we want to stop at that
    // expansion.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this usage of panic!() is deprecated; it will be a hard error in Rust 2021");
        l.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
        if !is_arg_inside_call(arg_span, span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note(format!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol_str).as_str());
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                Applicability::MaybeIncorrect,
            );
            if panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    let (span, _, _) = panic_call(cx, f);

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if is_arg_inside_call(arg.span, span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if is_arg_inside_call(arg.span, span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan { start: open, end: open + 1 }),
        span.from_inner(InnerSpan { start: close, end: close + 1 }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, SymbolStr) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    let macro_symbol =
        if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
    (expn.call_site, panic_macro, macro_symbol.as_str())
}

fn is_arg_inside_call(arg: Span, call: Span) -> bool {
    // We only add suggestions if the argument we're looking at appears inside the
    // panic call in the source file, to avoid invalid suggestions when macros are involved.
    // We specifically check for the spans to not be identical, as that happens sometimes when
    // proc_macros lie about spans and apply the same span to all the tokens they produce.
    call.contains(arg) && !call.source_equal(&arg)
}
<|endoftext|>


<commit_before>//! Integer operations and traits.

#![warn(bad_style, missing_docs,
        unused, unused_extern_crates, unused_import_braces,
        unused_qualifications, unused_results, unused_typecasts)]

#![feature(core)]

extern crate num;

use std::cmp::Ordering;
use std::num::{FromPrimitive, ToPrimitive};
use num::{One, Zero};

/// Extension methods for num::Integer trait.
pub trait Integer: num::Integer + Clone + FromPrimitive + ToPrimitive {

    /// Divide two numbers, return the result, rounded up.
    ///
    /// # Arguments
    ///
    /// * x - an integer
    /// * y - an integer distinct from 0u
    ///
    /// # Return value
    ///
    /// The smallest integer `q` such that `x/y <= q`.
    ///
    fn div_ceil(&self, other: &Self) -> Self {
        let div = self.clone() / other.clone();
        if self.is_multiple_of(other) {
            div
        } else {
            div + One::one()
        }
    }

    /// Divide two numbers, return the result, rounded to the closest integer.
    ///
    /// # Arguments
    ///
    /// * x - an integer
    /// * y - an integer distinct from 0u
    ///
    /// # Return value
    ///
    /// The integer `q` closest to `x/y`.
    ///
    fn div_round(&self, other: &Self) -> Self {
        let (div, rem) = self.div_rem(other);
        if rem.clone() + rem.clone() < other.clone() {
            div
        } else {
            div + One::one()
        }
    }

    /// Creates an iterator to enumerate each digit from the lower of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// let mut it = 12345i.into_digits(10);
    /// assert_eq!(Some(5), it.next());
    /// assert_eq!(Some(4), it.next());
    /// assert_eq!(Some(3), it.next());
    /// assert_eq!(Some(2), it.next());
    /// assert_eq!(Some(1), it.next());
    /// assert_eq!(None,    it.next());
    /// ```
    #[inline]
    fn into_digits(self, radix: Self) -> Digits<Self> {
        Digits::new(self, radix)
    }

    /// Creates a histogram of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    /// assert!([0, 1, 1, 1, 0, 0, 0, 0, 0, 0] == 123u.into_digit_histogram());
    /// assert!([0, 3, 0, 0, 0, 0, 0, 0, 0, 0] == 111u.into_digit_histogram());
    /// assert!([0, 0, 0, 0, 0, 0, 0, 0, 0, 0] == 0u.into_digit_histogram());
    /// ```
    #[inline]
    fn into_digit_histogram(self) -> [u32; 10] {
        let mut hist = [0; 10];
        let ten = FromPrimitive::from_u32(10).unwrap();
        for d in self.into_digits(ten) {
            hist[d.to_uint().unwrap()] += 1;
        }
        hist
    }

    /// Creates an integer from an iterator to enumerate each digit from the lower.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(321, Integer::from_digits(vec![1, 2, 3].into_iter(), 10i));
    /// assert_eq!(0x321, Integer::from_digits(vec![1, 2, 3].into_iter(), 16i));
    /// assert_eq!(0, Integer::from_digits(vec![].into_iter(), 10i));
    /// ```
    #[inline]
    fn from_digits<T: Iterator<Item = Self>>(digits: T, radix: Self) -> Self {
        let mut result: Self = Zero::zero();
        let mut order: Self = One::one();
        for d in digits {
            result = result + order.clone() * d;
            order = order * radix.clone();
        }
        result
    }

    /// Creates a palindromic number from `self`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(12321, 123i.into_palindromic(10, false));
    /// assert_eq!(123321, 123i.into_palindromic(10, true));
    /// ```
    #[inline]
    fn into_palindromic(self, radix: Self, duplicate_middle: bool) -> Self {
        let digits = self.into_digits(radix.clone());
        let mut rv = digits.clone().rev();
        if !duplicate_middle { let _ = rv.next_back(); }
        rv.chain(digits).fold(Zero::zero(), |sum: Self, i| sum * radix.clone() + i)
    }

    /// Returns `true` if the number is palindromic.
    ///
    /// # Example
    ///
    /// ```
    /// use integer::Integer;
    ///
    /// assert_eq!(true, 12321i.is_palindromic(10));
    /// assert_eq!(false, 12345i.is_palindromic(10));
    /// ```
    fn is_palindromic(self, radix: Self) -> bool {
        let mut digits = self.into_digits(radix);
        loop {
            let next = digits.next();
            let next_back = digits.next_back();
            if next.is_none() || next_back.is_none() {
                return true;
            }
            if next != next_back {
                return false;
            }
        }
    }

    /// Takes the square root of the number.
    #[inline]
    fn sqrt(&self) -> Self {
        let one: Self = One::one();
        let two: Self = one.clone() + one.clone();
        let mut min: Self = Zero::zero();
        let mut max: Self = self.clone();

        while min < max {
            let mid = (min.clone() + max.clone() + one.clone()) / two.clone();
            let mid2 = mid.clone() * mid.clone();
            match mid2.partial_cmp(self).unwrap() {
                Ordering::Equal => return mid,
                Ordering::Greater => { max = mid - one.clone() }
                Ordering::Less    => { min = mid }
            }
        }

        return min
    }

    /// Gets the factorial of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(1u, 0u.factorial());
    /// assert_eq!(1u, 1u.factorial());
    /// assert_eq!(2u, 2u.factorial());
    /// assert_eq!(6u, 3u.factorial());
    /// assert_eq!(24u, 4u.factorial());
    /// ```
    fn factorial(&self) -> Self {
        assert!(*self >= Zero::zero());

        let mut p: Self = One::one();
        let mut i: Self = One::one();
        while i <= *self {
            p = p * i.clone();
            i = i + One::one();
        }
        p
    }

    /// Takes the modular exponentation of the number.
    fn mod_pow(&self, exp: &Self, modulo: &Self) -> Self {
        let zero = Zero::zero();
        let one: Self  = One::one();
        let two: Self  = one.clone() + one.clone();
        if *self == zero { return zero }

        let mut result = one.clone();
        let mut base   = self.clone();
        let mut exp    = exp.clone();
        let     modulo = modulo.clone();

        while exp > zero {
            if exp.is_odd() {
                result = (result * base.clone()) % modulo.clone();
            }
            exp = exp / two.clone();
            base = (base.clone() * base) % modulo.clone();
        }
        result
    }
}

impl Integer for num::BigUint {}
impl Integer for num::BigInt {}
impl Integer for i8 {}
impl Integer for i16 {}
impl Integer for i32 {}
impl Integer for i64 {}
impl Integer for isize {}
impl Integer for u8 {}
impl Integer for u16 {}
impl Integer for u32 {}
impl Integer for u64 {}
impl Integer for usize {}

/// An iterator that enumerates each digit of a number.
#[derive(Clone)]
pub struct Digits<T> { num: T, radix: T, order: T }

impl<T: num::Integer + Clone> Digits<T> {
    fn new(num: T, radix: T) -> Digits<T> {
        let mut order: T;
        if num.is_zero() {
            order = Zero::zero();
        } else {
            order = One::one();
            let mut prod = order.clone() * radix.clone();
            while prod <= num {
                order = prod;
                prod = order.clone() * radix.clone();
            }
        }
        Digits { num: num, radix: radix, order: order }
    }
}

impl<T: num::Integer + Clone> Iterator for Digits<T> {
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<T> {
        if self.order.is_zero() { return None; }
        let (d, r) = self.num.div_rem(&self.radix);
        self.num   = d;
        self.order = self.order.clone() / self.radix.clone();
        Some(r)
    }
}

impl<T: num::Integer + Clone> DoubleEndedIterator for Digits<T> {
    #[inline]
    fn next_back(&mut self) -> Option<T> {
        if self.order.is_zero() { return None; }
        let (d, r) = self.num.div_rem(&self.order);
        self.num   = r;
        self.order = self.order.clone() / self.radix.clone();
        Some(d)
    }
}

#[cfg(test)]
mod tests {
    use super::Integer;
    use num;
    use num::Integer as NumInteger;

    #[test]
    fn div() {
        assert_eq!(0, 0.div_floor(&3));
        assert_eq!(0, 0.div_round(&3));
        assert_eq!(0, 0.div_ceil(&3));

        assert_eq!(0, 1.div_floor(&3));
        assert_eq!(0, 1.div_round(&3));
        assert_eq!(1, 1.div_ceil(&3));

        assert_eq!(0, 2.div_floor(&3));
        assert_eq!(1, 2.div_round(&3));
        assert_eq!(1, 2.div_ceil(&3));

        assert_eq!(1, 3.div_floor(&3));
        assert_eq!(1, 3.div_round(&3));
        assert_eq!(1, 3.div_ceil(&3));

        assert_eq!(1, 4.div_floor(&3));
        assert_eq!(1, 4.div_round(&3));
        assert_eq!(2, 4.div_ceil(&3));

        assert_eq!(1, 5.div_floor(&3));
        assert_eq!(2, 5.div_round(&3));
        assert_eq!(2, 5.div_ceil(&3));

        assert_eq!(3, 5.div_round(&2));
    }

    #[test]
    fn digits() {
        fn check(n: u32, v: &[u32], radix: u32) {
            assert_eq!(v, n.into_digits(radix).collect::<Vec<_>>());
            let mut rev = n.into_digits(radix).rev().collect::<Vec<_>>();
            rev.reverse();
            assert_eq!(v, &rev)
        }

        check(0, &[], 10);
        check(1, &[1], 10);
        check(3, &[3], 10);
        check(12345, &[5, 4, 3, 2, 1], 10);
        check(0x12345, &[5, 4, 3, 2, 1], 16);
        check(0xabcde, &[0xe, 0xd, 0xc, 0xb, 0xa], 16);
        check(0b11100, &[0, 0, 1, 1, 1], 2);
    }

    #[test]
    fn to_palindromic() {
        assert_eq!(101, 10.into_palindromic(10, false));
        assert_eq!(1001, 10.into_palindromic(10, true));

        assert_eq!(99999, 999.into_palindromic(10, false));
        assert_eq!(999999, 999.into_palindromic(10, true));

        assert_eq!(99099, 990.into_palindromic(10, false));
        assert_eq!(990099, 990.into_palindromic(10, true));

        assert_eq!(1100011, 1100.into_palindromic(10, false));
        assert_eq!(11000011, 1100.into_palindromic(10, true));

        assert_eq!(0xabcba, 0xabc.into_palindromic(16, false));
        assert_eq!(0xabccba, 0xabc.into_palindromic(16, true));
    }

    #[test]
    fn is_palindromic() {
        assert!(0.is_palindromic(10));
        assert!(1.is_palindromic(10));
        assert!(9.is_palindromic(10));
        assert!(11.is_palindromic(10));
        assert!(121.is_palindromic(10));
        assert!(!123.is_palindromic(10));
        assert!(1221.is_palindromic(10));
        assert!(12321.is_palindromic(10));
    }

    #[test]
    fn mod_pow() {
        for b in (1u32 .. 10) {
            for e in (0u32 .. 5) {
                for r in (10u32 .. 100) {
                    assert_eq!(num::pow(b, e as usize) % r, b.mod_pow(&e, &r));
                }
            }
        }
    }
}
<commit_msg>fix test failure<commit_after>//! Integer operations and traits.

#![warn(bad_style, missing_docs,
        unused, unused_extern_crates, unused_import_braces,
        unused_qualifications, unused_results, unused_typecasts)]

#![feature(core)]

extern crate num;

use std::cmp::Ordering;
use std::num::{FromPrimitive, ToPrimitive};
use num::{One, Zero};

/// Extension methods for num::Integer trait.
pub trait Integer: num::Integer + Clone + FromPrimitive + ToPrimitive {

    /// Divide two numbers, return the result, rounded up.
    ///
    /// # Arguments
    ///
    /// * x - an integer
    /// * y - an integer distinct from 0u
    ///
    /// # Return value
    ///
    /// The smallest integer `q` such that `x/y <= q`.
    ///
    fn div_ceil(&self, other: &Self) -> Self {
        let div = self.clone() / other.clone();
        if self.is_multiple_of(other) {
            div
        } else {
            div + One::one()
        }
    }

    /// Divide two numbers, return the result, rounded to the closest integer.
    ///
    /// # Arguments
    ///
    /// * x - an integer
    /// * y - an integer distinct from 0u
    ///
    /// # Return value
    ///
    /// The integer `q` closest to `x/y`.
    ///
    fn div_round(&self, other: &Self) -> Self {
        let (div, rem) = self.div_rem(other);
        if rem.clone() + rem.clone() < other.clone() {
            div
        } else {
            div + One::one()
        }
    }

    /// Creates an iterator to enumerate each digit from the lower of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// let mut it = 12345i.into_digits(10);
    /// assert_eq!(Some(5), it.next());
    /// assert_eq!(Some(4), it.next());
    /// assert_eq!(Some(3), it.next());
    /// assert_eq!(Some(2), it.next());
    /// assert_eq!(Some(1), it.next());
    /// assert_eq!(None,    it.next());
    /// ```
    #[inline]
    fn into_digits(self, radix: Self) -> Digits<Self> {
        Digits::new(self, radix)
    }

    /// Creates a histogram of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    /// assert!([0, 1, 1, 1, 0, 0, 0, 0, 0, 0] == 123u.into_digit_histogram());
    /// assert!([0, 3, 0, 0, 0, 0, 0, 0, 0, 0] == 111u.into_digit_histogram());
    /// assert!([0, 0, 0, 0, 0, 0, 0, 0, 0, 0] == 0u.into_digit_histogram());
    /// ```
    #[inline]
    fn into_digit_histogram(self) -> [u32; 10] {
        let mut hist = [0; 10];
        let ten = FromPrimitive::from_u32(10).unwrap();
        for d in self.into_digits(ten) {
            hist[d.to_uint().unwrap()] += 1;
        }
        hist
    }

    /// Creates an integer from an iterator to enumerate each digit from the lower.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(321, Integer::from_digits(vec![1, 2, 3].into_iter(), 10i));
    /// assert_eq!(0x321, Integer::from_digits(vec![1, 2, 3].into_iter(), 16i));
    /// assert_eq!(0, Integer::from_digits(vec![].into_iter(), 10i));
    /// ```
    #[inline]
    fn from_digits<T: Iterator<Item = Self>>(digits: T, radix: Self) -> Self {
        let mut result: Self = Zero::zero();
        let mut order: Self = One::one();
        for d in digits {
            result = result + order.clone() * d;
            order = order * radix.clone();
        }
        result
    }

    /// Creates a palindromic number from `self`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(12321, 123i.into_palindromic(10, false));
    /// assert_eq!(123321, 123i.into_palindromic(10, true));
    /// ```
    #[inline]
    fn into_palindromic(self, radix: Self, duplicate_middle: bool) -> Self {
        let digits = self.into_digits(radix.clone());
        let mut rv = digits.clone().rev();
        if !duplicate_middle { let _ = rv.next_back(); }
        rv.chain(digits).fold(Zero::zero(), |sum: Self, i| sum * radix.clone() + i)
    }

    /// Returns `true` if the number is palindromic.
    ///
    /// # Example
    ///
    /// ```
    /// use integer::Integer;
    ///
    /// assert_eq!(true, 12321i.is_palindromic(10));
    /// assert_eq!(false, 12345i.is_palindromic(10));
    /// ```
    fn is_palindromic(self, radix: Self) -> bool {
        let mut digits = self.into_digits(radix);
        loop {
            let next = digits.next();
            let next_back = digits.next_back();
            if next.is_none() || next_back.is_none() {
                return true;
            }
            if next != next_back {
                return false;
            }
        }
    }

    /// Takes the square root of the number.
    #[inline]
    fn sqrt(&self) -> Self {
        let one: Self = One::one();
        let two: Self = one.clone() + one.clone();
        let mut min: Self = Zero::zero();
        let mut max: Self = self.clone();

        while min < max {
            let mid = (min.clone() + max.clone() + one.clone()) / two.clone();
            let mid2 = mid.clone() * mid.clone();
            match mid2.partial_cmp(self).unwrap() {
                Ordering::Equal => return mid,
                Ordering::Greater => { max = mid - one.clone() }
                Ordering::Less    => { min = mid }
            }
        }

        return min
    }

    /// Gets the factorial of the number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use integer::Integer;
    ///
    /// assert_eq!(1u, 0u.factorial());
    /// assert_eq!(1u, 1u.factorial());
    /// assert_eq!(2u, 2u.factorial());
    /// assert_eq!(6u, 3u.factorial());
    /// assert_eq!(24u, 4u.factorial());
    /// ```
    fn factorial(&self) -> Self {
        assert!(*self >= Zero::zero());

        let mut p: Self = One::one();
        let mut i: Self = One::one();
        while i <= *self {
            p = p * i.clone();
            i = i + One::one();
        }
        p
    }

    /// Takes the modular exponentation of the number.
    fn mod_pow(&self, exp: &Self, modulo: &Self) -> Self {
        let zero = Zero::zero();
        let one: Self  = One::one();
        let two: Self  = one.clone() + one.clone();
        if *self == zero { return zero }

        let mut result = one.clone();
        let mut base   = self.clone();
        let mut exp    = exp.clone();
        let     modulo = modulo.clone();

        while exp > zero {
            if exp.is_odd() {
                result = (result * base.clone()) % modulo.clone();
            }
            exp = exp / two.clone();
            base = (base.clone() * base) % modulo.clone();
        }
        result
    }
}

impl Integer for num::BigUint {}
impl Integer for num::BigInt {}
impl Integer for i8 {}
impl Integer for i16 {}
impl Integer for i32 {}
impl Integer for i64 {}
impl Integer for isize {}
impl Integer for u8 {}
impl Integer for u16 {}
impl Integer for u32 {}
impl Integer for u64 {}
impl Integer for usize {}

/// An iterator that enumerates each digit of a number.
#[derive(Clone)]
pub struct Digits<T> { num: T, radix: T, order: T }

impl<T: num::Integer + Clone> Digits<T> {
    fn new(num: T, radix: T) -> Digits<T> {
        let mut order: T;
        if num.is_zero() {
            order = Zero::zero();
        } else {
            order = One::one();
            let mut prod = order.clone() * radix.clone();
            while prod <= num {
                order = prod;
                prod = order.clone() * radix.clone();
            }
        }
        Digits { num: num, radix: radix, order: order }
    }
}

impl<T: num::Integer + Clone> Iterator for Digits<T> {
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<T> {
        if self.order.is_zero() { return None; }
        let (d, r) = self.num.div_rem(&self.radix);
        self.num   = d;
        self.order = self.order.clone() / self.radix.clone();
        Some(r)
    }
}

impl<T: num::Integer + Clone> DoubleEndedIterator for Digits<T> {
    #[inline]
    fn next_back(&mut self) -> Option<T> {
        if self.order.is_zero() { return None; }
        let (d, r) = self.num.div_rem(&self.order);
        self.num   = r;
        self.order = self.order.clone() / self.radix.clone();
        Some(d)
    }
}

#[cfg(test)]
mod tests {
    use super::Integer;
    use num;
    use num::Integer as NumInteger;

    #[test]
    fn div() {
        assert_eq!(0, 0.div_floor(&3));
        assert_eq!(0, 0.div_round(&3));
        assert_eq!(0, 0.div_ceil(&3));

        assert_eq!(0, 1.div_floor(&3));
        assert_eq!(0, 1.div_round(&3));
        assert_eq!(1, 1.div_ceil(&3));

        assert_eq!(0, 2.div_floor(&3));
        assert_eq!(1, 2.div_round(&3));
        assert_eq!(1, 2.div_ceil(&3));

        assert_eq!(1, 3.div_floor(&3));
        assert_eq!(1, 3.div_round(&3));
        assert_eq!(1, 3.div_ceil(&3));

        assert_eq!(1, 4.div_floor(&3));
        assert_eq!(1, 4.div_round(&3));
        assert_eq!(2, 4.div_ceil(&3));

        assert_eq!(1, 5.div_floor(&3));
        assert_eq!(2, 5.div_round(&3));
        assert_eq!(2, 5.div_ceil(&3));

        assert_eq!(3, 5.div_round(&2));
    }

    #[test]
    fn digits() {
        fn check(n: u32, v: &[u32], radix: u32) {
            assert_eq!(v, n.into_digits(radix).collect::<Vec<_>>());
            let mut rev = n.into_digits(radix).rev().collect::<Vec<_>>();
            rev.reverse();
            assert_eq!(v, rev)
        }

        check(0, &[], 10);
        check(1, &[1], 10);
        check(3, &[3], 10);
        check(12345, &[5, 4, 3, 2, 1], 10);
        check(0x12345, &[5, 4, 3, 2, 1], 16);
        check(0xabcde, &[0xe, 0xd, 0xc, 0xb, 0xa], 16);
        check(0b11100, &[0, 0, 1, 1, 1], 2);
    }

    #[test]
    fn to_palindromic() {
        assert_eq!(101, 10.into_palindromic(10, false));
        assert_eq!(1001, 10.into_palindromic(10, true));

        assert_eq!(99999, 999.into_palindromic(10, false));
        assert_eq!(999999, 999.into_palindromic(10, true));

        assert_eq!(99099, 990.into_palindromic(10, false));
        assert_eq!(990099, 990.into_palindromic(10, true));

        assert_eq!(1100011, 1100.into_palindromic(10, false));
        assert_eq!(11000011, 1100.into_palindromic(10, true));

        assert_eq!(0xabcba, 0xabc.into_palindromic(16, false));
        assert_eq!(0xabccba, 0xabc.into_palindromic(16, true));
    }

    #[test]
    fn is_palindromic() {
        assert!(0.is_palindromic(10));
        assert!(1.is_palindromic(10));
        assert!(9.is_palindromic(10));
        assert!(11.is_palindromic(10));
        assert!(121.is_palindromic(10));
        assert!(!123.is_palindromic(10));
        assert!(1221.is_palindromic(10));
        assert!(12321.is_palindromic(10));
    }

    #[test]
    fn mod_pow() {
        for b in (1u32 .. 10) {
            for e in (0u32 .. 5) {
                for r in (10u32 .. 100) {
                    assert_eq!(num::pow(b, e as usize) % r, b.mod_pow(&e, &r));
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>use super::*;

pub struct QuotesFilter {
    pub locale: String,
}

impl LinterFilter for QuotesFilter {
    fn locales(&self) -> Vec<&'static str> {
        vec!(
            "de",
            "en",
            "es",
            "fr",
            "it"
        )
    }

    fn message(&self) -> &'static str {
        match &self.locale as &str {
            "de" => "Please use german quotation marks without spaces.",
            "en" => "Please use english double quotation marks without spaces.",
            "es" => "Please use french quotation marks without spaces.",
            "fr" => "Please use french quotation marks with non-breaking spaces.",
            "it" => "Please use french quotation marks without spaces.",
            _ => unimplemented!(),
        }
    }

    fn regex_pattern(&self) -> String {
        let pattern = match &self.locale as &str {
            "de" => "(\".+\")|(«.+»)|(“.+”)|(„[\\s].+[\\s]“)",
            "en" => "(\".+\")|(«.+»)|(“[\\s].+[\\s]”)|(„.+“)",
            "es" => "(\".+\")|(«[\\s].+[\\s]»)|(“.+”)|(„.+“)",
            "fr" => "(\".+\")|(«[^ ].+[^ ]»)|(“.+”)|(„.+“)",
            "it" => "(\".+\")|(«\\s.+\\s»)|(“.+”)|(„.+“)",
            _ => unimplemented!(),
        };

        pattern.to_string()
    }
}

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

    #[test]
    fn test_filters_when_de_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("\"Ich auch\", sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(10, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_french_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("«Ich auch», sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(12, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("“Ich auch”, sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(14, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_german_quotation_marks_and_spaces() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("„ Ich auch “, sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filter_when_de_and_no_warnings() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("„Ich auch“, sagte der italienische");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_es_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("\"Y yo también\", dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(15, warnings[0].end);
    }

    #[test]
    fn test_filters_when_es_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("“Y yo también”, dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(19, warnings[0].end);
    }

    #[test]
    fn test_filters_when_es_and_french_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("« Y yo también », dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(19, warnings[0].end);
    }

    #[test]
    fn test_filter_when_es_and_no_warnings() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("«Y yo también», dijo el italiano");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_en_and_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("\"Mee too\", said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(9, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_french_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("« Mee too », said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(15, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_english_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("“ Mee too ”, said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_german_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("„Mee too“, said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(13, warnings[0].end);
    }

    #[test]
    fn test_filter_when_en_and_no_warnings() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("“Mee too”, said the French.");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_fr_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("\"Et moi aussi\", dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(14, warnings[0].end);
    }

    #[test]
    fn test_filters_when_fr_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("“Et moi aussi”, dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(18, warnings[0].end);
    }

    #[test]
    fn test_filters_when_fr_and_french_quotation_marks_without_non_breaking_spaces() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("«Et moi aussi», dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filter_when_fr_and_no_warnings() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("« Et moi aussi », dit l’Anglais.");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_it_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("\"Anche a me\", ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(12, warnings[0].end);
    }

    #[test]
    fn test_filters_when_it_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("“Anche a me”, ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filters_when_it_and_french_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("« Anche a me », ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(18, warnings[0].end);
    }

    #[test]
    fn test_filter_when_it_and_no_warnings() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("«Anche a me», ha detto la spagnola");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }
}

<commit_msg>Fix match_same_arms clippy warning<commit_after>use super::*;

pub struct QuotesFilter {
    pub locale: String,
}

impl LinterFilter for QuotesFilter {
    fn locales(&self) -> Vec<&'static str> {
        vec!(
            "de",
            "en",
            "es",
            "fr",
            "it"
        )
    }

    fn message(&self) -> &'static str {
        match &self.locale as &str {
            "de" => "Please use german quotation marks without spaces.",
            "en" => "Please use english double quotation marks without spaces.",
            "es" | "it" => "Please use french quotation marks without spaces.",
            "fr" => "Please use french quotation marks with non-breaking spaces.",
            _ => unimplemented!(),
        }
    }

    fn regex_pattern(&self) -> String {
        let pattern = match &self.locale as &str {
            "de" => "(\".+\")|(«.+»)|(“.+”)|(„[\\s].+[\\s]“)",
            "en" => "(\".+\")|(«.+»)|(“[\\s].+[\\s]”)|(„.+“)",
            "es" => "(\".+\")|(«[\\s].+[\\s]»)|(“.+”)|(„.+“)",
            "fr" => "(\".+\")|(«[^ ].+[^ ]»)|(“.+”)|(„.+“)",
            "it" => "(\".+\")|(«\\s.+\\s»)|(“.+”)|(„.+“)",
            _ => unimplemented!(),
        };

        pattern.to_string()
    }
}

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

    #[test]
    fn test_filters_when_de_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("\"Ich auch\", sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(10, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_french_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("«Ich auch», sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(12, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("“Ich auch”, sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(14, warnings[0].end);
    }

    #[test]
    fn test_filters_when_de_and_german_quotation_marks_and_spaces() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("„ Ich auch “, sagte der italienische");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use german quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filter_when_de_and_no_warnings() {
        let filter = QuotesFilter { locale: "de".to_string() };

        let result = filter.check("„Ich auch“, sagte der italienische");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_es_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("\"Y yo también\", dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(15, warnings[0].end);
    }

    #[test]
    fn test_filters_when_es_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("“Y yo también”, dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(19, warnings[0].end);
    }

    #[test]
    fn test_filters_when_es_and_french_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("« Y yo también », dijo el italiano");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(19, warnings[0].end);
    }

    #[test]
    fn test_filter_when_es_and_no_warnings() {
        let filter = QuotesFilter { locale: "es".to_string() };

        let result = filter.check("«Y yo también», dijo el italiano");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_en_and_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("\"Mee too\", said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(9, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_french_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("« Mee too », said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(15, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_english_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("“ Mee too ”, said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filters_when_en_and_and_german_quotation_marks() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("„Mee too“, said the French.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use english double quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(13, warnings[0].end);
    }

    #[test]
    fn test_filter_when_en_and_no_warnings() {
        let filter = QuotesFilter { locale: "en".to_string() };

        let result = filter.check("“Mee too”, said the French.");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_fr_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("\"Et moi aussi\", dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(14, warnings[0].end);
    }

    #[test]
    fn test_filters_when_fr_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("“Et moi aussi”, dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(18, warnings[0].end);
    }

    #[test]
    fn test_filters_when_fr_and_french_quotation_marks_without_non_breaking_spaces() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("«Et moi aussi», dit l’Anglais.");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks with non-breaking spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filter_when_fr_and_no_warnings() {
        let filter = QuotesFilter { locale: "fr".to_string() };

        let result = filter.check("« Et moi aussi », dit l’Anglais.");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }

    #[test]
    fn test_filters_when_it_and_straight_quotation_marks() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("\"Anche a me\", ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(12, warnings[0].end);
    }

    #[test]
    fn test_filters_when_it_and_english_quotation_marks() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("“Anche a me”, ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(16, warnings[0].end);
    }

    #[test]
    fn test_filters_when_it_and_french_quotation_marks_with_spaces() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("« Anche a me », ha detto la spagnola");

        assert!(result.is_err());

        let warnings = result.err().unwrap();

        assert_eq!(1, warnings.len());
        assert_eq!("Please use french quotation marks without spaces.", warnings[0].message);
        assert_eq!(0, warnings[0].start);
        assert_eq!(18, warnings[0].end);
    }

    #[test]
    fn test_filter_when_it_and_no_warnings() {
        let filter = QuotesFilter { locale: "it".to_string() };

        let result = filter.check("«Anche a me», ha detto la spagnola");

        assert_eq!(false, result.is_err());
        assert_eq!((), result.unwrap());
    }
}

<|endoftext|>


<commit_before>// TODO: dpi
// TODO: close events (stdweb PR required)
// TODO: pointer locking (stdweb PR required)
// TODO: mouse wheel events (stdweb PR required)
// TODO: key event: .which() (stdweb PR)
// TODO: should there be a maximization / fullscreen API?

mod device;
mod error;
mod event_loop;
mod monitor;
mod window;

#[cfg(feature = "web-sys")]
#[path = "web_sys/mod.rs"]
mod backend;

#[cfg(feature = "stdweb")]
#[path = "stdweb/mod.rs"]
mod backend;

pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{
    EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
};
pub use self::monitor::Handle as MonitorHandle;
pub use self::window::{
    Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
    Window,
};
<commit_msg>Update the web backend todo list<commit_after>// TODO: close events (port from old stdweb branch)
// TODO: pointer locking (stdweb PR required)
// TODO: fullscreen API (stdweb PR required)

mod device;
mod error;
mod event_loop;
mod monitor;
mod window;

#[cfg(feature = "web-sys")]
#[path = "web_sys/mod.rs"]
mod backend;

#[cfg(feature = "stdweb")]
#[path = "stdweb/mod.rs"]
mod backend;

pub use self::device::Id as DeviceId;
pub use self::error::OsError;
pub use self::event_loop::{
    EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget,
};
pub use self::monitor::Handle as MonitorHandle;
pub use self::window::{
    Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes,
    Window,
};
<|endoftext|>


<commit_before>//! Extension methods for `compose_yml::v2::Service`.

use compose_yml::v2 as dc;
use shlex;
use std::vec;

use errors::*;
#[cfg(test)]
use project::Project;
use sources::{self, Source};
use ext::context::ContextExt;
use util::err;

/// These methods will appear as regular methods on `Service` in any module
/// which includes `ServiceExt`.
pub trait ServiceExt {
    /// The build context associated with this service (either a git
    /// repository URL or a local directory).
    fn context(&self) -> Result<Option<&dc::Context>>;

    /// The directory in which to mount our source code if it's checked
    /// out.
    fn source_mount_dir(&self) -> Result<String>;

    /// The subdirectory inside the source where the code for this service is located.
    /// `Ok(None)` either means that this service has no build context, or that
    /// its context is not a git repository, or that its context is a git repository
    /// without a subdirectory. `Ok(Some(dir)` means that the service's build context
    /// is a git repository, with a subdirectory of `dir`.
    fn repository_subdirectory(&self) -> Result<Option<String>>;

    /// Get the default shell associated with this service.  Used for
    /// getting interactive access to a container.
    fn shell(&self) -> Result<String>;

    /// Get the test command associated with this service.
    fn test_command(&self) -> Result<Vec<String>>;

    /// All the `Source` trees which can be mounted into this `Service`.
    /// Note that this iterator does not hold any references to this
    /// `Service` object, so you can use it to decide how you want to
    /// update other fields of this object without running afoul of the
    /// borrow checker.
    fn sources<'a, 'b>(&'a self,
                       sources: &'b sources::Sources)
                       -> Result<Sources<'b>>;
}

impl ServiceExt for dc::Service {
    fn context(&self) -> Result<Option<&dc::Context>> {
        if let Some(ref build) = self.build {
            Ok(Some(build.context.value()?))
        } else {
            Ok(None)
        }
    }

    fn source_mount_dir(&self) -> Result<String> {
        let default = dc::escape("/app")?;
        let srcdir = self.labels
            .get("io.fdy.cage.srcdir")
            .unwrap_or_else(|| &default);
        Ok(srcdir.value()?.to_owned())
    }

    fn repository_subdirectory(&self) -> Result<Option<String>> {
        if let Some(context) = self.context()? {
            return match *context {
                dc::Context::Dir(_) => Ok(None),
                dc::Context::GitUrl(ref git_url) => {
                    Ok(git_url.subdirectory().map(|subdir| subdir.to_string()))
                },
            }
        }
        Ok(None)
    }

    fn shell(&self) -> Result<String> {
        let default = dc::escape("sh")?;
        let shell = self.labels
            .get("io.fdy.cage.shell")
            .unwrap_or_else(|| &default);
        Ok(shell.value()?.to_owned())
    }

    fn test_command(&self) -> Result<Vec<String>> {
        let raw = self.labels
            .get("io.fdy.cage.test")
            .ok_or_else(|| {
                err("specify a value for the label io.fdy.cage.test to run tests")
            })?;
        let mut lexer = shlex::Shlex::new(raw.value()?);
        let result: Vec<String> = lexer.by_ref().map(|w| w.to_owned()).collect();
        if lexer.had_error {
            Err(err!("cannot parse <{}> into shell words", raw))
        } else {
            Ok(result)
        }
    }

    fn sources<'a, 'b>(&'a self,
                       sources: &'b sources::Sources)
                       -> Result<Sources<'b>> {
        // Get our `context`, if any.
        let container_path = self.source_mount_dir()?;
        let source_subdirectory = self.repository_subdirectory()?;

        let context = self.context()?
            .and_then(|ctx| {
                // human_alias is called on every context when constructing Sources
                let alias = &ctx.human_alias().expect("human_alias failed on a context that worked previously");
                sources.find_by_alias(alias)
            })
            .and_then(|source| {
                Some(SourceMount { container_path, source, source_subdirectory })
            });

        // Get our library keys and mount points.
        let mut libs = vec![];
        for (label, mount_as) in &self.labels {
            let prefix = "io.fdy.cage.lib.";
            if label.starts_with(prefix) {
                let lib_name = label[prefix.len()..].to_string();
                let source: Result<&Source> = sources
                    .find_by_lib_key(&lib_name)
                    .ok_or_else(|| ErrorKind::UnknownLibKey(lib_name).into());

                libs.push(SourceMount {
                    container_path: mount_as.value()?.to_owned(),
                    source: source?,
                    source_subdirectory: None,
                })
            }
        }

        Ok(Sources { context, libs: libs.into_iter() })
    }
}

/// Iterator over all the `Source` trees which can be mounted into this
/// `Service`.
pub struct Sources<'a> {
    /// Do we need to iterate over our `context` field?
    context: Option<SourceMount<'a>>,
    /// Libraries
    libs: vec::IntoIter<SourceMount<'a>>,
}

#[derive(Clone)]
pub struct SourceMount<'a> {
    pub container_path: String,
    pub source: &'a Source,
    pub source_subdirectory: Option<String>,
}

impl<'a> Iterator for Sources<'a> {
    type Item = SourceMount<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        // Check for a `SourceMount` using `take`, which moves data out of
        // an `Option` value and leaves `None` in its place,
        // simulataneously updating our internal state and keeping the
        // borrow checker happy.
        self.context.take().or_else(|| {
             // If there is no SourceMount for the service itself, iterate over libs
            self.libs.next()
        })
    }
}

#[test]
fn src_dir_returns_the_source_directory_for_this_service() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_example("rails_hello").unwrap();
    let target = proj.target("development").unwrap();

    // Default value.
    let db = proj.pod("db").unwrap();
    let merged = db.merged_file(target).unwrap();
    let db = merged.services.get("db").unwrap();
    assert_eq!(db.source_mount_dir().unwrap(), "/app");

    // Custom value.
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();
    let proxy = merged.services.get("web").unwrap();
    assert_eq!(proxy.source_mount_dir().unwrap(), "/usr/src/app");
}

#[test]
fn build_context_can_specify_a_subdirectory() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_fixture("with_repo_subdir").unwrap();
    let target = proj.target("development").unwrap();

    // Default value.
    let db = proj.pod("db").unwrap();
    let merged = db.merged_file(target).unwrap();
    let db = merged.services.get("db").unwrap();
    assert_eq!(db.repository_subdirectory().unwrap(), None);

    // Custom value.
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();
    let web = merged.services.get("web").unwrap();
    assert_eq!(web.repository_subdirectory().unwrap(), Some("myfolder".to_string()));
}

#[test]
fn shell_returns_preferred_shell_for_this_service() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_example("hello").unwrap();
    let target = proj.target("development").unwrap();
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();

    // Default value.
    let web = merged.services.get("web").unwrap();
    assert_eq!(web.shell().unwrap(), "sh");

    // Custom value.
    let proxy = merged.services.get("proxy").unwrap();
    assert_eq!(proxy.shell().unwrap(), "/bin/sh");
}<commit_msg>Use explicit lambda type to remove need for intermediate Result variable<commit_after>//! Extension methods for `compose_yml::v2::Service`.

use compose_yml::v2 as dc;
use shlex;
use std::vec;

use errors::*;
#[cfg(test)]
use project::Project;
use sources::{self, Source};
use ext::context::ContextExt;
use util::err;

/// These methods will appear as regular methods on `Service` in any module
/// which includes `ServiceExt`.
pub trait ServiceExt {
    /// The build context associated with this service (either a git
    /// repository URL or a local directory).
    fn context(&self) -> Result<Option<&dc::Context>>;

    /// The directory in which to mount our source code if it's checked
    /// out.
    fn source_mount_dir(&self) -> Result<String>;

    /// The subdirectory inside the source where the code for this service is located.
    /// `Ok(None)` either means that this service has no build context, or that
    /// its context is not a git repository, or that its context is a git repository
    /// without a subdirectory. `Ok(Some(dir)` means that the service's build context
    /// is a git repository, with a subdirectory of `dir`.
    fn repository_subdirectory(&self) -> Result<Option<String>>;

    /// Get the default shell associated with this service.  Used for
    /// getting interactive access to a container.
    fn shell(&self) -> Result<String>;

    /// Get the test command associated with this service.
    fn test_command(&self) -> Result<Vec<String>>;

    /// All the `Source` trees which can be mounted into this `Service`.
    /// Note that this iterator does not hold any references to this
    /// `Service` object, so you can use it to decide how you want to
    /// update other fields of this object without running afoul of the
    /// borrow checker.
    fn sources<'a, 'b>(&'a self,
                       sources: &'b sources::Sources)
                       -> Result<Sources<'b>>;
}

impl ServiceExt for dc::Service {
    fn context(&self) -> Result<Option<&dc::Context>> {
        if let Some(ref build) = self.build {
            Ok(Some(build.context.value()?))
        } else {
            Ok(None)
        }
    }

    fn source_mount_dir(&self) -> Result<String> {
        let default = dc::escape("/app")?;
        let srcdir = self.labels
            .get("io.fdy.cage.srcdir")
            .unwrap_or_else(|| &default);
        Ok(srcdir.value()?.to_owned())
    }

    fn repository_subdirectory(&self) -> Result<Option<String>> {
        if let Some(context) = self.context()? {
            return match *context {
                dc::Context::Dir(_) => Ok(None),
                dc::Context::GitUrl(ref git_url) => {
                    Ok(git_url.subdirectory().map(|subdir| subdir.to_string()))
                },
            }
        }
        Ok(None)
    }

    fn shell(&self) -> Result<String> {
        let default = dc::escape("sh")?;
        let shell = self.labels
            .get("io.fdy.cage.shell")
            .unwrap_or_else(|| &default);
        Ok(shell.value()?.to_owned())
    }

    fn test_command(&self) -> Result<Vec<String>> {
        let raw = self.labels
            .get("io.fdy.cage.test")
            .ok_or_else(|| {
                err("specify a value for the label io.fdy.cage.test to run tests")
            })?;
        let mut lexer = shlex::Shlex::new(raw.value()?);
        let result: Vec<String> = lexer.by_ref().map(|w| w.to_owned()).collect();
        if lexer.had_error {
            Err(err!("cannot parse <{}> into shell words", raw))
        } else {
            Ok(result)
        }
    }

    fn sources<'a, 'b>(&'a self,
                       sources: &'b sources::Sources)
                       -> Result<Sources<'b>> {
        // Get our `context`, if any.
        let container_path = self.source_mount_dir()?;
        let source_subdirectory = self.repository_subdirectory()?;

        let context = self.context()?
            .and_then(|ctx| {
                // human_alias is called on every context when constructing Sources
                let alias = &ctx.human_alias().expect("human_alias failed on a context that worked previously");
                sources.find_by_alias(alias)
            })
            .and_then(|source| {
                Some(SourceMount { container_path, source, source_subdirectory })
            });

        // Get our library keys and mount points.
        let mut libs = vec![];
        for (label, mount_as) in &self.labels {
            let prefix = "io.fdy.cage.lib.";
            if label.starts_with(prefix) {
                let lib_name = label[prefix.len()..].to_string();
                let source = sources
                    .find_by_lib_key(&lib_name)
                    .ok_or_else(|| -> Error { ErrorKind::UnknownLibKey(lib_name).into() })?;

                libs.push(SourceMount {
                    container_path: mount_as.value()?.to_owned(),
                    source,
                    source_subdirectory: None,
                })
            }
        }

        Ok(Sources { context, libs: libs.into_iter() })
    }
}

/// Iterator over all the `Source` trees which can be mounted into this
/// `Service`.
pub struct Sources<'a> {
    /// Do we need to iterate over our `context` field?
    context: Option<SourceMount<'a>>,
    /// Libraries
    libs: vec::IntoIter<SourceMount<'a>>,
}

#[derive(Clone)]
pub struct SourceMount<'a> {
    pub container_path: String,
    pub source: &'a Source,
    pub source_subdirectory: Option<String>,
}

impl<'a> Iterator for Sources<'a> {
    type Item = SourceMount<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        // Check for a `SourceMount` using `take`, which moves data out of
        // an `Option` value and leaves `None` in its place,
        // simulataneously updating our internal state and keeping the
        // borrow checker happy.
        self.context.take().or_else(|| {
             // If there is no SourceMount for the service itself, iterate over libs
            self.libs.next()
        })
    }
}

#[test]
fn src_dir_returns_the_source_directory_for_this_service() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_example("rails_hello").unwrap();
    let target = proj.target("development").unwrap();

    // Default value.
    let db = proj.pod("db").unwrap();
    let merged = db.merged_file(target).unwrap();
    let db = merged.services.get("db").unwrap();
    assert_eq!(db.source_mount_dir().unwrap(), "/app");

    // Custom value.
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();
    let proxy = merged.services.get("web").unwrap();
    assert_eq!(proxy.source_mount_dir().unwrap(), "/usr/src/app");
}

#[test]
fn build_context_can_specify_a_subdirectory() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_fixture("with_repo_subdir").unwrap();
    let target = proj.target("development").unwrap();

    // Default value.
    let db = proj.pod("db").unwrap();
    let merged = db.merged_file(target).unwrap();
    let db = merged.services.get("db").unwrap();
    assert_eq!(db.repository_subdirectory().unwrap(), None);

    // Custom value.
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();
    let web = merged.services.get("web").unwrap();
    assert_eq!(web.repository_subdirectory().unwrap(), Some("myfolder".to_string()));
}

#[test]
fn shell_returns_preferred_shell_for_this_service() {
    use env_logger;
    let _ = env_logger::init();
    let proj: Project = Project::from_example("hello").unwrap();
    let target = proj.target("development").unwrap();
    let frontend = proj.pod("frontend").unwrap();
    let merged = frontend.merged_file(target).unwrap();

    // Default value.
    let web = merged.services.get("web").unwrap();
    assert_eq!(web.shell().unwrap(), "sh");

    // Custom value.
    let proxy = merged.services.get("proxy").unwrap();
    assert_eq!(proxy.shell().unwrap(), "/bin/sh");
}<|endoftext|>