<commit_before>use rustc::hir;
use rustc::lint::*;
use rustc::ty;
use syntax::codemap::Span;
use utils::{in_macro, iter_input_pats, match_type, method_chain_args, snippet, span_lint_and_then};
use utils::paths;

#[derive(Clone)]
pub struct Pass;

/// **What it does:** Checks for usage of `Option.map(f)` where f is a nil
/// function or closure
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if statement
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let x : Option<&str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
/// The correct use would be:
/// ```rust
/// let x : Option<&str> = do_stuff();
/// if let Some(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Some(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_lint! {
    pub OPTION_MAP_NIL_FN,
    Allow,
    "using `Option.map(f)`, where f is a nil function or closure"
}

impl LintPass for Pass {
    fn get_lints(&self) -> LintArray {
        lint_array!(OPTION_MAP_NIL_FN)
    }
}

fn is_nil_type(ty: ty::Ty) -> bool {
    match ty.sty {
        ty::TyTuple(slice) => slice.is_empty(),
        ty::TyNever => true,
        _ => false,
    }
}

fn is_nil_function(cx: &LateContext, expr: &hir::Expr) -> bool {
    let ty = cx.tables.expr_ty(expr);

    if let ty::TyFnDef(_, _, bare) = ty.sty {
        if let Some(fn_type) = cx.tcx.no_late_bound_regions(&bare.sig) {
            return is_nil_type(fn_type.output());
        }
    }
    false
}

fn is_nil_expression(cx: &LateContext, expr: &hir::Expr) -> bool {
    is_nil_type(cx.tables.expr_ty(expr))
}

// The expression inside a closure may or may not have surrounding braces and
// semicolons, which causes problems when generating a suggestion. Given an
// expression that evaluates to '()' or '!', recursively remove useless braces
// and semi-colons until is suitable for including in the suggestion template
fn reduce_nil_expression<'a>(cx: &LateContext, expr: &'a hir::Expr) -> Option<Span> {
    if !is_nil_expression(cx, expr) {
        return None;
    }

    match expr.node {
        hir::ExprCall(_, _) |
        hir::ExprMethodCall(_, _, _) => {
            // Calls can't be reduced any more
            Some(expr.span)
        },
        hir::ExprBlock(ref block) => {
            match (&block.stmts[..], block.expr.as_ref()) {
                (&[], Some(inner_expr)) => {
                    // Reduce `{ X }` to `X`
                    reduce_nil_expression(cx, inner_expr)
                },
                (&[ref inner_stmt], None) => {
                    // Reduce `{ X; }` to `X` or `X;`
                    match inner_stmt.node {
                        hir::StmtDecl(ref d, _) => Some(d.span),
                        hir::StmtExpr(ref e, _) => Some(e.span),
                        hir::StmtSemi(ref e, _) => {
                            if is_nil_expression(cx, e) {
                                // `X` returns nil so we can strip the
                                // semicolon and reduce further
                                reduce_nil_expression(cx, e)
                            } else {
                                // `X` doesn't return nil so it needs a
                                // trailing semicolon
                                Some(inner_stmt.span)
                            }
                        },
                    }
                },
                _ => None,
            }
        },
        _ => None,
    }
}

fn nil_closure<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'a hir::Expr) -> Option<(&'tcx hir::Arg, &'a hir::Expr)> {
    if let hir::ExprClosure(_, ref decl, inner_expr_id, _) = expr.node {
        let body = cx.tcx.map.body(inner_expr_id);
        let body_expr = &body.value;

        if_let_chain! {[
            decl.inputs.len() == 1,
            is_nil_expression(cx, body_expr),
            let Some(binding) = iter_input_pats(&decl, body).next(),
        ], {
            return Some((binding, body_expr))
        }}
    }
    None
}

fn lint_map_nil_fn(cx: &LateContext, stmt: &hir::Stmt, expr: &hir::Expr, map_args: &[hir::Expr]) {
    let var_arg = &map_args[0];
    let fn_arg = &map_args[1];

    if !match_type(cx, cx.tables.expr_ty(var_arg), &paths::OPTION) {
        return;
    }

    if is_nil_function(cx, fn_arg) {
        let msg = "called `map(f)` on an Option value where `f` is a nil function";
        let suggestion = format!("if let Some(...) = {0} {{ {1}(...) }}",
                                 snippet(cx, var_arg.span, "_"),
                                 snippet(cx, fn_arg.span, "_"));

        span_lint_and_then(cx,
                           OPTION_MAP_NIL_FN,
                           expr.span,
                           msg,
                           |db| { db.span_suggestion(stmt.span, "try this", suggestion); });
    } else if let Some((binding, closure_expr)) = nil_closure(cx, fn_arg) {
        let msg = "called `map(f)` on an Option value where `f` is a nil closure";
        let suggestion = if let Some(expr_span) = reduce_nil_expression(cx, closure_expr) {
            format!("if let Some({0}) = {1} {{ {2} }}",
                    snippet(cx, binding.pat.span, "_"),
                    snippet(cx, var_arg.span, "_"),
                    snippet(cx, expr_span, "_"))
        } else {
            format!("if let Some({0}) = {1} {{ ... }}",
                    snippet(cx, binding.pat.span, "_"),
                    snippet(cx, var_arg.span, "_"))
        };

        span_lint_and_then(cx,
                           OPTION_MAP_NIL_FN,
                           expr.span,
                           msg,
                           |db| { db.span_suggestion(stmt.span, "try this", suggestion); });
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
    fn check_stmt(&mut self, cx: &LateContext, stmt: &hir::Stmt) {
        if in_macro(cx, stmt.span) {
            return;
        }

        if let hir::StmtSemi(ref expr, _) = stmt.node {
            if let hir::ExprMethodCall(_, _, _) = expr.node {
                if let Some(arglists) = method_chain_args(expr, &["map"]) {
                    lint_map_nil_fn(cx, stmt, expr, arglists[0]);
                }
            }
        }
    }
}
<commit_msg>Make it compile again<commit_after>use rustc::hir;
use rustc::lint::*;
use rustc::ty;
use syntax::codemap::Span;
use utils::{in_macro, iter_input_pats, match_type, method_chain_args, snippet, span_lint_and_then};
use utils::paths;

#[derive(Clone)]
pub struct Pass;

/// **What it does:** Checks for usage of `Option.map(f)` where f is a nil
/// function or closure
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if statement
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let x : Option<&str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
/// The correct use would be:
/// ```rust
/// let x : Option<&str> = do_stuff();
/// if let Some(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Some(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_lint! {
    pub OPTION_MAP_NIL_FN,
    Allow,
    "using `Option.map(f)`, where f is a nil function or closure"
}

impl LintPass for Pass {
    fn get_lints(&self) -> LintArray {
        lint_array!(OPTION_MAP_NIL_FN)
    }
}

fn is_nil_type(ty: ty::Ty) -> bool {
    match ty.sty {
        ty::TyTuple(slice) => slice.is_empty(),
        ty::TyNever => true,
        _ => false,
    }
}

fn is_nil_function(cx: &LateContext, expr: &hir::Expr) -> bool {
    let ty = cx.tables.expr_ty(expr);

    if let ty::TyFnDef(id, _) = ty.sty {
        if let Some(fn_type) = cx.tcx.fn_sig(id).no_late_bound_regions() {
            return is_nil_type(fn_type.output());
        }
    }
    false
}

fn is_nil_expression(cx: &LateContext, expr: &hir::Expr) -> bool {
    is_nil_type(cx.tables.expr_ty(expr))
}

// The expression inside a closure may or may not have surrounding braces and
// semicolons, which causes problems when generating a suggestion. Given an
// expression that evaluates to '()' or '!', recursively remove useless braces
// and semi-colons until is suitable for including in the suggestion template
fn reduce_nil_expression<'a>(cx: &LateContext, expr: &'a hir::Expr) -> Option<Span> {
    if !is_nil_expression(cx, expr) {
        return None;
    }

    match expr.node {
        hir::ExprCall(_, _) |
        hir::ExprMethodCall(_, _, _) => {
            // Calls can't be reduced any more
            Some(expr.span)
        },
        hir::ExprBlock(ref block) => {
            match (&block.stmts[..], block.expr.as_ref()) {
                (&[], Some(inner_expr)) => {
                    // Reduce `{ X }` to `X`
                    reduce_nil_expression(cx, inner_expr)
                },
                (&[ref inner_stmt], None) => {
                    // Reduce `{ X; }` to `X` or `X;`
                    match inner_stmt.node {
                        hir::StmtDecl(ref d, _) => Some(d.span),
                        hir::StmtExpr(ref e, _) => Some(e.span),
                        hir::StmtSemi(ref e, _) => {
                            if is_nil_expression(cx, e) {
                                // `X` returns nil so we can strip the
                                // semicolon and reduce further
                                reduce_nil_expression(cx, e)
                            } else {
                                // `X` doesn't return nil so it needs a
                                // trailing semicolon
                                Some(inner_stmt.span)
                            }
                        },
                    }
                },
                _ => None,
            }
        },
        _ => None,
    }
}

fn nil_closure<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'a hir::Expr) -> Option<(&'tcx hir::Arg, &'a hir::Expr)> {
    if let hir::ExprClosure(_, ref decl, inner_expr_id, _, _) = expr.node {
        let body = cx.tcx.hir.body(inner_expr_id);
        let body_expr = &body.value;

        if_chain! {
            if decl.inputs.len() == 1;
            if is_nil_expression(cx, body_expr);
            if let Some(binding) = iter_input_pats(&decl, body).next();
            then {
                return Some((binding, body_expr));
            }
        }
    }
    None
}

fn lint_map_nil_fn(cx: &LateContext, stmt: &hir::Stmt, expr: &hir::Expr, map_args: &[hir::Expr]) {
    let var_arg = &map_args[0];
    let fn_arg = &map_args[1];

    if !match_type(cx, cx.tables.expr_ty(var_arg), &paths::OPTION) {
        return;
    }

    if is_nil_function(cx, fn_arg) {
        let msg = "called `map(f)` on an Option value where `f` is a nil function";
        let suggestion = format!("if let Some(...) = {0} {{ {1}(...) }}",
                                 snippet(cx, var_arg.span, "_"),
                                 snippet(cx, fn_arg.span, "_"));

        span_lint_and_then(cx,
                           OPTION_MAP_NIL_FN,
                           expr.span,
                           msg,
                           |db| { db.span_suggestion(stmt.span, "try this", suggestion); });
    } else if let Some((binding, closure_expr)) = nil_closure(cx, fn_arg) {
        let msg = "called `map(f)` on an Option value where `f` is a nil closure";
        let suggestion = if let Some(expr_span) = reduce_nil_expression(cx, closure_expr) {
            format!("if let Some({0}) = {1} {{ {2} }}",
                    snippet(cx, binding.pat.span, "_"),
                    snippet(cx, var_arg.span, "_"),
                    snippet(cx, expr_span, "_"))
        } else {
            format!("if let Some({0}) = {1} {{ ... }}",
                    snippet(cx, binding.pat.span, "_"),
                    snippet(cx, var_arg.span, "_"))
        };

        span_lint_and_then(cx,
                           OPTION_MAP_NIL_FN,
                           expr.span,
                           msg,
                           |db| { db.span_suggestion(stmt.span, "try this", suggestion); });
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
    fn check_stmt(&mut self, cx: &LateContext, stmt: &hir::Stmt) {
        if in_macro(stmt.span) {
            return;
        }

        if let hir::StmtSemi(ref expr, _) = stmt.node {
            if let hir::ExprMethodCall(_, _, _) = expr.node {
                if let Some(arglists) = method_chain_args(expr, &["map"]) {
                    lint_map_nil_fn(cx, stmt, expr, arglists[0]);
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Read configurations files.

#![deny(clippy::missing_docs_in_private_items)]

use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
use rustc_span::source_map;
use source_map::Span;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs, io};

/// Gets the configuration file from arguments.
pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
    for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
        if arg.has_name(sym!(conf_file)) {
            return match arg.kind {
                MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
                MetaItemKind::NameValue(ref value) => {
                    if let LitKind::Str(ref file, _) = value.kind {
                        Ok(Some(file.to_string().into()))
                    } else {
                        Err(("`conf_file` value must be a string", value.span))
                    }
                },
            };
        }
    }

    Ok(None)
}

/// Error from reading a configuration file.
#[derive(Debug)]
pub enum Error {
    /// An I/O error.
    Io(io::Error),
    /// Not valid toml or doesn't fit the expected config format
    Toml(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io(err) => err.fmt(f),
            Self::Toml(err) => err.fmt(f),
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Self::Io(e)
    }
}

/// Vec of errors that might be collected during config toml parsing
static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));

macro_rules! define_Conf {
    ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
        mod helpers {
            use serde::Deserialize;
            /// Type used to store lint configuration.
            #[derive(Deserialize)]
            #[serde(rename_all = "kebab-case", deny_unknown_fields)]
            pub struct Conf {
                $(
                    #[$doc]
                    #[serde(default = $config_str)]
                    #[serde(with = $config_str)]
                    pub $config: $Ty,
                )+
                #[allow(dead_code)]
                #[serde(default)]
                third_party: Option<::toml::Value>,
            }

            $(
                mod $config {
                    use serde::Deserialize;
                    pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
                        use super::super::{ERRORS, Error};

                        Ok(
                            <$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
                                ERRORS
                                    .lock()
                                    .expect("no threading here")
                                    .push(Error::Toml(e.to_string()));
                                super::$config()
                            })
                        )
                    }
                }

                #[must_use]
                fn $config() -> $Ty {
                    let x = $default;
                    x
                }
            )+
        }
    };
}

pub use self::helpers::Conf;
define_Conf! {
    /// Lint: MANUAL_NON_EXHAUSTIVE, MANUAL_STRIP, OPTION_AS_REF_DEREF, MATCH_LIKE_MATCHES_MACRO. The minimum rust version that the project supports
    (msrv, "msrv": Option<String>, None),
    /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
    (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
    /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
    (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
    /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
    (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
    /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
    (doc_valid_idents, "doc_valid_idents": Vec<String>, [
        "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
        "DirectX",
        "ECMAScript",
        "GPLv2", "GPLv3",
        "GitHub", "GitLab",
        "IPv4", "IPv6",
        "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
        "NaN", "NaNs",
        "OAuth", "GraphQL",
        "OCaml",
        "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
        "TensorFlow",
        "TrueType",
        "iOS", "macOS",
        "TeX", "LaTeX", "BibTeX", "BibLaTeX",
        "MinGW",
        "CamelCase",
    ].iter().map(ToString::to_string).collect()),
    /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
    (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
    /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
    (type_complexity_threshold, "type_complexity_threshold": u64, 250),
    /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
    (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
    /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
    (too_large_for_stack, "too_large_for_stack": u64, 200),
    /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
    (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
    /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
    (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
    /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
    (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
    /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
    (literal_representation_threshold, "literal_representation_threshold": u64, 16384),
    /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
    (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
    /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
    (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256),
    /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
    (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
    /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
    (array_size_threshold, "array_size_threshold": u64, 512_000),
    /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
    (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
    /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
    (max_trait_bounds, "max_trait_bounds": u64, 3),
    /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
    (max_struct_bools, "max_struct_bools": u64, 3),
    /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
    (max_fn_params_bools, "max_fn_params_bools": u64, 3),
    /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
    (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
    /// Lint: DISALLOWED_METHOD. The list of blacklisted methods to lint about. NB: `bar` is not here since it has legitimate uses
    (disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
}

impl Default for Conf {
    #[must_use]
    fn default() -> Self {
        toml::from_str("").expect("we never error on empty config files")
    }
}

/// Search for the configuration file.
pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
    /// Possible filename to search for.
    const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];

    // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
    // If neither of those exist, use ".".
    let mut current = env::var_os("CLIPPY_CONF_DIR")
        .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
        .map_or_else(|| PathBuf::from("."), PathBuf::from);
    loop {
        for config_file_name in &CONFIG_FILE_NAMES {
            let config_file = current.join(config_file_name);
            match fs::metadata(&config_file) {
                // Only return if it's a file to handle the unlikely situation of a directory named
                // `clippy.toml`.
                Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
                // Return the error if it's something other than `NotFound`; otherwise we didn't
                // find the project file yet, and continue searching.
                Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
                _ => {},
            }
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Ok(None);
        }
    }
}

/// Produces a `Conf` filled with the default values and forwards the errors
///
/// Used internally for convenience
fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
    (Conf::default(), errors)
}

/// Read the `toml` configuration file.
///
/// In case of error, the function tries to continue as much as possible.
pub fn read(path: &Path) -> (Conf, Vec<Error>) {
    let content = match fs::read_to_string(path) {
        Ok(content) => content,
        Err(err) => return default(vec![err.into()]),
    };

    assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
    match toml::from_str(&content) {
        Ok(toml) => {
            let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);

            let toml_ref: &Conf = &toml;

            let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;

            if cyc_field.is_some() {
                let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
                errors.push(Error::Toml(cyc_err));
            }

            (toml, errors)
        },
        Err(e) => {
            let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
            errors.push(Error::Toml(e.to_string()));

            default(errors)
        },
    }
}
<commit_msg>add WebGL to doc_valid_idents<commit_after>//! Read configurations files.

#![deny(clippy::missing_docs_in_private_items)]

use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
use rustc_span::source_map;
use source_map::Span;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs, io};

/// Gets the configuration file from arguments.
pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
    for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
        if arg.has_name(sym!(conf_file)) {
            return match arg.kind {
                MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
                MetaItemKind::NameValue(ref value) => {
                    if let LitKind::Str(ref file, _) = value.kind {
                        Ok(Some(file.to_string().into()))
                    } else {
                        Err(("`conf_file` value must be a string", value.span))
                    }
                },
            };
        }
    }

    Ok(None)
}

/// Error from reading a configuration file.
#[derive(Debug)]
pub enum Error {
    /// An I/O error.
    Io(io::Error),
    /// Not valid toml or doesn't fit the expected config format
    Toml(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io(err) => err.fmt(f),
            Self::Toml(err) => err.fmt(f),
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Self::Io(e)
    }
}

/// Vec of errors that might be collected during config toml parsing
static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));

macro_rules! define_Conf {
    ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
        mod helpers {
            use serde::Deserialize;
            /// Type used to store lint configuration.
            #[derive(Deserialize)]
            #[serde(rename_all = "kebab-case", deny_unknown_fields)]
            pub struct Conf {
                $(
                    #[$doc]
                    #[serde(default = $config_str)]
                    #[serde(with = $config_str)]
                    pub $config: $Ty,
                )+
                #[allow(dead_code)]
                #[serde(default)]
                third_party: Option<::toml::Value>,
            }

            $(
                mod $config {
                    use serde::Deserialize;
                    pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
                        use super::super::{ERRORS, Error};

                        Ok(
                            <$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
                                ERRORS
                                    .lock()
                                    .expect("no threading here")
                                    .push(Error::Toml(e.to_string()));
                                super::$config()
                            })
                        )
                    }
                }

                #[must_use]
                fn $config() -> $Ty {
                    let x = $default;
                    x
                }
            )+
        }
    };
}

pub use self::helpers::Conf;
define_Conf! {
    /// Lint: MANUAL_NON_EXHAUSTIVE, MANUAL_STRIP, OPTION_AS_REF_DEREF, MATCH_LIKE_MATCHES_MACRO. The minimum rust version that the project supports
    (msrv, "msrv": Option<String>, None),
    /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
    (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
    /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
    (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
    /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
    (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
    /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
    (doc_valid_idents, "doc_valid_idents": Vec<String>, [
        "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
        "DirectX",
        "ECMAScript",
        "GPLv2", "GPLv3",
        "GitHub", "GitLab",
        "IPv4", "IPv6",
        "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
        "NaN", "NaNs",
        "OAuth", "GraphQL",
        "OCaml",
        "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
        "WebGL",
        "TensorFlow",
        "TrueType",
        "iOS", "macOS",
        "TeX", "LaTeX", "BibTeX", "BibLaTeX",
        "MinGW",
        "CamelCase",
    ].iter().map(ToString::to_string).collect()),
    /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
    (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
    /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
    (type_complexity_threshold, "type_complexity_threshold": u64, 250),
    /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
    (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
    /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
    (too_large_for_stack, "too_large_for_stack": u64, 200),
    /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
    (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
    /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
    (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
    /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
    (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
    /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
    (literal_representation_threshold, "literal_representation_threshold": u64, 16384),
    /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
    (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
    /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
    (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256),
    /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
    (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
    /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
    (array_size_threshold, "array_size_threshold": u64, 512_000),
    /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
    (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
    /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
    (max_trait_bounds, "max_trait_bounds": u64, 3),
    /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
    (max_struct_bools, "max_struct_bools": u64, 3),
    /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
    (max_fn_params_bools, "max_fn_params_bools": u64, 3),
    /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
    (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
    /// Lint: DISALLOWED_METHOD. The list of blacklisted methods to lint about. NB: `bar` is not here since it has legitimate uses
    (disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
}

impl Default for Conf {
    #[must_use]
    fn default() -> Self {
        toml::from_str("").expect("we never error on empty config files")
    }
}

/// Search for the configuration file.
pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
    /// Possible filename to search for.
    const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];

    // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
    // If neither of those exist, use ".".
    let mut current = env::var_os("CLIPPY_CONF_DIR")
        .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
        .map_or_else(|| PathBuf::from("."), PathBuf::from);
    loop {
        for config_file_name in &CONFIG_FILE_NAMES {
            let config_file = current.join(config_file_name);
            match fs::metadata(&config_file) {
                // Only return if it's a file to handle the unlikely situation of a directory named
                // `clippy.toml`.
                Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
                // Return the error if it's something other than `NotFound`; otherwise we didn't
                // find the project file yet, and continue searching.
                Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
                _ => {},
            }
        }

        // If the current directory has no parent, we're done searching.
        if !current.pop() {
            return Ok(None);
        }
    }
}

/// Produces a `Conf` filled with the default values and forwards the errors
///
/// Used internally for convenience
fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
    (Conf::default(), errors)
}

/// Read the `toml` configuration file.
///
/// In case of error, the function tries to continue as much as possible.
pub fn read(path: &Path) -> (Conf, Vec<Error>) {
    let content = match fs::read_to_string(path) {
        Ok(content) => content,
        Err(err) => return default(vec![err.into()]),
    };

    assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
    match toml::from_str(&content) {
        Ok(toml) => {
            let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);

            let toml_ref: &Conf = &toml;

            let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;

            if cyc_field.is_some() {
                let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
                errors.push(Error::Toml(cyc_err));
            }

            (toml, errors)
        },
        Err(e) => {
            let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
            errors.push(Error::Toml(e.to_string()));

            default(errors)
        },
    }
}
<|endoftext|>


<commit_before>use super::util::{AuthToken,
                  BldrUrl,
                  CacheKeyPath,
                  ConfigOptAuthToken,
                  ConfigOptBldrUrl,
                  ConfigOptCacheKeyPath};
use crate::cli::{valid_origin,
                 valid_role};
use configopt::ConfigOpt;
use habitat_core::crypto::keys::PairType;
use std::path::PathBuf;
use structopt::{clap::ArgGroup,
                StructOpt};

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands relating to Habitat Builder origins
pub enum Origin {
    /// Creates a new Builder origin
    Create {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin to be created
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Removes an unused/empty origin
    Delete {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Departs membership from selected origin
    Depart {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Displays general information about an origin
    Info {
        /// The origin name to be queried
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        /// Output will be rendered in json
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        to_json:    bool,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    Invitations(Invitations),
    Key(Key),
    /// Role Based Access Control for origin members
    Rbac(Rbac),
    Secret(Secret),
    /// Transfers ownership of an origin to another member of that origin
    Transfer {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:            String,
        #[structopt(flatten)]
        bldr_url:          BldrUrl,
        #[structopt(flatten)]
        auth_token:        AuthToken,
        /// The account name of the new origin owner
        #[structopt(name = "NEW_OWNER_ACCOUNT")]
        new_owner_account: String,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Manage origin member invitations
pub enum Invitations {
    /// Accept an origin member invitation
    Accept {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to accept
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// Ignore an origin member invitation
    Ignore {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to ignore
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// List origin invitations sent to your account
    List {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// List pending invitations for a particular origin. Requires that you are the origin owner
    Pending {
        /// The name of the origin you wish to list invitations for
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Rescind an existing origin member invitation
    Rescind {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to rescind
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// Send an origin member invitation
    Send {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:          String,
        /// The account name to invite into the origin
        #[structopt(name = "INVITEE_ACCOUNT")]
        invitee_account: String,
        #[structopt(flatten)]
        bldr_url:        BldrUrl,
        #[structopt(flatten)]
        auth_token:      AuthToken,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands relating to Habitat origin key maintenance
pub enum Key {
    /// Download origin key(s)
    Download {
        #[structopt(flatten)]
        cache_key_path:  CacheKeyPath,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:          String,
        /// The origin key revision
        #[structopt(name = "REVISION")]
        revision:        Option<String>,
        #[structopt(flatten)]
        bldr_url:        BldrUrl,
        /// Download origin private key instead of origin public key
        #[structopt(name = "WITH_SECRET", short = "s", long = "secret")]
        with_secret:     bool,
        /// Download public encryption key instead of origin public key
        #[structopt(name = "WITH_ENCRYPTION", short = "e", long = "encryption")]
        with_encryption: bool,
        /// Authentication token for Builder (required for downloading origin private keys)
        #[structopt(name = "AUTH_TOKEN", short = "z", long = "auth")]
        auth_token:      Option<String>,
    },
    /// Outputs the latest origin key contents to stdout
    Export {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:         String,
        /// Export either the 'public' or 'secret' key. The 'secret' key is the origin private key
        #[structopt(name = "PAIR_TYPE", short = "t", long = "type")]
        pair_type:      Option<PairType>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Generates a Habitat origin key pair
    Generate {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Reads a stdin stream containing a public or private origin key contents and writes the key
    /// to disk
    Import {
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Upload origin keys to Builder
    Upload {
        #[structopt(flatten)]
        upload:         UploadGroup,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
        /// Upload origin private key in addition to the public key
        #[structopt(name = "WITH_SECRET",
                    short = "s",
                    long = "secret",
                    conflicts_with = "PUBLIC_FILE")]
        with_secret:    bool,
        /// Path to a local origin private key file on disk
        #[structopt(name = "SECRET_FILE", long = "secfile", conflicts_with = "ORIGIN")]
        secret_file:    Option<PathBuf>,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
    },
}

#[derive(ConfigOpt, StructOpt, Debug)]
#[structopt(group = ArgGroup::with_name("upload").required(true), no_version)]
pub struct UploadGroup {
    /// The origin name
    #[structopt(name = "ORIGIN", validator = valid_origin, group = "upload")]
    origin:      Option<String>,
    /// Path to a local public origin key file on disk
    #[structopt(name = "PUBLIC_FILE", long = "pubfile", group = "upload")]
    public_file: Option<PathBuf>,
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands related to origin rbac
pub enum Rbac {
    /// Display an origin member's current role
    Show {
        /// The account name of the role to display
        #[structopt(name = "MEMBER_ACCOUNT")]
        member_account: String,
        /// The origin name the member account belongs to
        #[structopt(name = "ORIGIN",
                short = "o",
                long = "origin",
                validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        to_json:        bool,
    },
    Set {
        /// The account name of the role to display
        #[structopt(name = "MEMBER_ACCOUNT")]
        member_account: String,
        /// The origin name the member account belongs to
        #[structopt(name = "ORIGIN",
                short = "o",
                long = "origin",
                validator = valid_origin)]
        origin:         Option<String>,
        /// The role to set for the member account
        #[structopt(name = "ROLE",
                short = "r",
                long = "role",
                required = true,
                validator = valid_role)]
        role:           String,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        /// Output will be rendered in json
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        /// Do not prompt for confirmation
        #[structopt(name = "NO_PROMPT", short = "n", long = "no-prompt")]
        no_prompt:      bool,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands related to secret management
pub enum Secret {
    /// Delete a secret for your origin
    Delete {
        /// The name of the variable key to be injected into the studio
        #[structopt(name = "KEY_NAME")]
        key_name:   String,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
        /// The origin for which the secret will be deleted. Default is from 'HAB_ORIGIN' or
        /// cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:     Option<String>,
    },
    /// List all secrets for your origin
    List {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
        /// The origin for which secrets will be listed. Default is from 'HAB_ORIGIN' or cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:     Option<String>,
    },
    /// Create and upload a secret for your origin
    Upload {
        /// The name of the variable key to be injected into the studio. Ex: KEY="some_value"
        #[structopt(name = "KEY_NAME")]
        key_name:       String,
        /// The contents of the variable to be injected into the studio
        #[structopt(name = "SECRET")]
        secret:         String,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        /// The origin for which the secret will be uploaded. Default is from 'HAB_ORIGIN' or
        /// cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
}
<commit_msg>add missing discription for set configopt variant<commit_after>use super::util::{AuthToken,
                  BldrUrl,
                  CacheKeyPath,
                  ConfigOptAuthToken,
                  ConfigOptBldrUrl,
                  ConfigOptCacheKeyPath};
use crate::cli::{valid_origin,
                 valid_role};
use configopt::ConfigOpt;
use habitat_core::crypto::keys::PairType;
use std::path::PathBuf;
use structopt::{clap::ArgGroup,
                StructOpt};

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands relating to Habitat Builder origins
pub enum Origin {
    /// Creates a new Builder origin
    Create {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin to be created
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Removes an unused/empty origin
    Delete {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Departs membership from selected origin
    Depart {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Displays general information about an origin
    Info {
        /// The origin name to be queried
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        /// Output will be rendered in json
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        to_json:    bool,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    Invitations(Invitations),
    Key(Key),
    /// Role Based Access Control for origin members
    Rbac(Rbac),
    Secret(Secret),
    /// Transfers ownership of an origin to another member of that origin
    Transfer {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:            String,
        #[structopt(flatten)]
        bldr_url:          BldrUrl,
        #[structopt(flatten)]
        auth_token:        AuthToken,
        /// The account name of the new origin owner
        #[structopt(name = "NEW_OWNER_ACCOUNT")]
        new_owner_account: String,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Manage origin member invitations
pub enum Invitations {
    /// Accept an origin member invitation
    Accept {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to accept
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// Ignore an origin member invitation
    Ignore {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to ignore
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// List origin invitations sent to your account
    List {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// List pending invitations for a particular origin. Requires that you are the origin owner
    Pending {
        /// The name of the origin you wish to list invitations for
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:     String,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
    },
    /// Rescind an existing origin member invitation
    Rescind {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:        String,
        /// The id of the invitation to rescind
        #[structopt(name = "INVITATION_ID")]
        invitation_id: u64,
        #[structopt(flatten)]
        bldr_url:      BldrUrl,
        #[structopt(flatten)]
        auth_token:    AuthToken,
    },
    /// Send an origin member invitation
    Send {
        /// The origin name the invitation applies to
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:          String,
        /// The account name to invite into the origin
        #[structopt(name = "INVITEE_ACCOUNT")]
        invitee_account: String,
        #[structopt(flatten)]
        bldr_url:        BldrUrl,
        #[structopt(flatten)]
        auth_token:      AuthToken,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands relating to Habitat origin key maintenance
pub enum Key {
    /// Download origin key(s)
    Download {
        #[structopt(flatten)]
        cache_key_path:  CacheKeyPath,
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:          String,
        /// The origin key revision
        #[structopt(name = "REVISION")]
        revision:        Option<String>,
        #[structopt(flatten)]
        bldr_url:        BldrUrl,
        /// Download origin private key instead of origin public key
        #[structopt(name = "WITH_SECRET", short = "s", long = "secret")]
        with_secret:     bool,
        /// Download public encryption key instead of origin public key
        #[structopt(name = "WITH_ENCRYPTION", short = "e", long = "encryption")]
        with_encryption: bool,
        /// Authentication token for Builder (required for downloading origin private keys)
        #[structopt(name = "AUTH_TOKEN", short = "z", long = "auth")]
        auth_token:      Option<String>,
    },
    /// Outputs the latest origin key contents to stdout
    Export {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:         String,
        /// Export either the 'public' or 'secret' key. The 'secret' key is the origin private key
        #[structopt(name = "PAIR_TYPE", short = "t", long = "type")]
        pair_type:      Option<PairType>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Generates a Habitat origin key pair
    Generate {
        /// The origin name
        #[structopt(name = "ORIGIN", validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Reads a stdin stream containing a public or private origin key contents and writes the key
    /// to disk
    Import {
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
    /// Upload origin keys to Builder
    Upload {
        #[structopt(flatten)]
        upload:         UploadGroup,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
        /// Upload origin private key in addition to the public key
        #[structopt(name = "WITH_SECRET",
                    short = "s",
                    long = "secret",
                    conflicts_with = "PUBLIC_FILE")]
        with_secret:    bool,
        /// Path to a local origin private key file on disk
        #[structopt(name = "SECRET_FILE", long = "secfile", conflicts_with = "ORIGIN")]
        secret_file:    Option<PathBuf>,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
    },
}

#[derive(ConfigOpt, StructOpt, Debug)]
#[structopt(group = ArgGroup::with_name("upload").required(true), no_version)]
pub struct UploadGroup {
    /// The origin name
    #[structopt(name = "ORIGIN", validator = valid_origin, group = "upload")]
    origin:      Option<String>,
    /// Path to a local public origin key file on disk
    #[structopt(name = "PUBLIC_FILE", long = "pubfile", group = "upload")]
    public_file: Option<PathBuf>,
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands related to origin rbac
pub enum Rbac {
    /// Display an origin member's current role
    Show {
        /// The account name of the role to display
        #[structopt(name = "MEMBER_ACCOUNT")]
        member_account: String,
        /// The origin name the member account belongs to
        #[structopt(name = "ORIGIN",
                short = "o",
                long = "origin",
                validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        to_json:        bool,
    },
    /// Change an origin member's role
    Set {
        /// The account name of the role to display
        #[structopt(name = "MEMBER_ACCOUNT")]
        member_account: String,
        /// The origin name the member account belongs to
        #[structopt(name = "ORIGIN",
                short = "o",
                long = "origin",
                validator = valid_origin)]
        origin:         Option<String>,
        /// The role to set for the member account
        #[structopt(name = "ROLE",
                short = "r",
                long = "role",
                required = true,
                validator = valid_role)]
        role:           String,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        /// Output will be rendered in json
        #[structopt(name = "TO_JSON", short = "j", long = "json")]
        /// Do not prompt for confirmation
        #[structopt(name = "NO_PROMPT", short = "n", long = "no-prompt")]
        no_prompt:      bool,
    },
}

#[derive(ConfigOpt, StructOpt)]
#[structopt(no_version)]
/// Commands related to secret management
pub enum Secret {
    /// Delete a secret for your origin
    Delete {
        /// The name of the variable key to be injected into the studio
        #[structopt(name = "KEY_NAME")]
        key_name:   String,
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
        /// The origin for which the secret will be deleted. Default is from 'HAB_ORIGIN' or
        /// cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:     Option<String>,
    },
    /// List all secrets for your origin
    List {
        #[structopt(flatten)]
        bldr_url:   BldrUrl,
        #[structopt(flatten)]
        auth_token: AuthToken,
        /// The origin for which secrets will be listed. Default is from 'HAB_ORIGIN' or cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:     Option<String>,
    },
    /// Create and upload a secret for your origin
    Upload {
        /// The name of the variable key to be injected into the studio. Ex: KEY="some_value"
        #[structopt(name = "KEY_NAME")]
        key_name:       String,
        /// The contents of the variable to be injected into the studio
        #[structopt(name = "SECRET")]
        secret:         String,
        #[structopt(flatten)]
        bldr_url:       BldrUrl,
        #[structopt(flatten)]
        auth_token:     AuthToken,
        /// The origin for which the secret will be uploaded. Default is from 'HAB_ORIGIN' or
        /// cli.toml
        #[structopt(name = "ORIGIN", short = "o", long = "origin", validator = valid_origin)]
        origin:         Option<String>,
        #[structopt(flatten)]
        cache_key_path: CacheKeyPath,
    },
}
<|endoftext|>


<commit_before>use std::borrow::Cow::{Owned, Borrowed};

use pulldown_cmark as cmark;
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
use slug::slugify;
use syntect::easy::HighlightLines;
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};

use errors::Result;
use utils::site::resolve_internal_link;
use highlighting::{get_highlighter, THEME_SET};
use link_checker::check_url;

use table_of_contents::{TempHeader, Header, make_table_of_contents};
use context::RenderContext;

const CONTINUE_READING: &str = "

\n";

#[derive(Debug)]
pub struct Rendered {
    pub body: String,
    pub summary_len: Option<usize>,
    pub toc: Vec
}

// We might have cases where the slug is already present in our list of anchor
// for example an article could have several titles named Example
// We add a counter after the slug if the slug is already present, which
// means we will have example, example-1, example-2 etc
fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
    if level == 0 && !anchors.contains(&name) {
        return name.to_string();
    }

    let new_anchor = format!("{}-{}", name, level + 1);
    if !anchors.contains(&new_anchor) {
        return new_anchor;
    }

    find_anchor(anchors, name, level + 1)
}

fn is_colocated_asset_link(link: &str) -> bool {
    !link.contains("/")  // http://, ftp://, ../ etc
        && !link.starts_with("mailto:")
}

pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Rendered> {
    // the rendered html
    let mut html = String::with_capacity(content.len());
    // Set while parsing
    let mut error = None;

    let mut background = IncludeBackground::Yes;
    let mut highlighter: Option<HighlightLines> = None;
    // If we get text in header, we need to insert the id and a anchor
    let mut in_header = false;
    // pulldown_cmark can send several text events for a title if there are markdown
    // specific characters like `!` in them. We only want to insert the anchor the first time
    let mut header_created = false;
    let mut anchors: Vec<String> = vec![];

    let mut headers = vec![];
    // Defaults to a 0 level so not a real header
    // It should be an Option ideally but not worth the hassle to update
    let mut temp_header = TempHeader::default();

    let mut opts = Options::empty();
    let mut has_summary = false;
    opts.insert(OPTION_ENABLE_TABLES);
    opts.insert(OPTION_ENABLE_FOOTNOTES);

    {
        let parser = Parser::new_ext(content, opts).map(|event| {
            match event {
                Event::Text(text) => {
                    // Header first
                    if in_header {
                        if header_created {
                            temp_header.push(&text);
                            return Event::Html(Borrowed(""));
                        }
                        let id = find_anchor(&anchors, slugify(&text), 0);
                        anchors.push(id.clone());
                        // update the header and add it to the list
                        temp_header.permalink = format!("{}#{}", context.current_page_permalink, id);
                        temp_header.id = id;
                        // += as we might have some  or other things already there
                        temp_header.title += &text;
                        header_created = true;
                        return Event::Html(Borrowed(""));
                    }

                    // if we are in the middle of a code block
                    if let Some(ref mut highlighter) = highlighter {
                        let highlighted = &highlighter.highlight(&text);
                        let html = styles_to_coloured_html(highlighted, background);
                        return Event::Html(Owned(html));
                    }

                    // Business as usual
                    Event::Text(text)
                }
                Event::Start(Tag::CodeBlock(ref info)) => {
                    if !context.config.highlight_code {
                        return Event::Html(Borrowed("
"));
                    }

                    let theme = &THEME_SET.themes[&context.config.highlight_theme];
                    match get_highlighter(&theme, info, context.base_path, &context.config.extra_syntaxes) {
                        Ok(h) => {
                            highlighter = Some(h);
                            // This selects the background color the same way that start_coloured_html_snippet does
                            let color = theme.settings.background.unwrap_or(::syntect::highlighting::Color::WHITE);
                            background = IncludeBackground::IfDifferent(color);
                        }
                        Err(err) => {
                            error = Some(format!("Could not load syntax: {}", err).into());
                            return Event::Html(Borrowed(""));
                        }
                    }
                    let snippet = start_coloured_html_snippet(theme);
                    Event::Html(Owned(snippet))
                }
                Event::End(Tag::CodeBlock(_)) => {
                    if !context.config.highlight_code {
                        return Event::Html(Borrowed("
\n"));
                    }
                    // reset highlight and close the code block
                    highlighter = None;
                    Event::Html(Borrowed("
"))
                }
                Event::Start(Tag::Image(src, title)) => {
                    if is_colocated_asset_link(&src) {
                        return Event::Start(
                            Tag::Image(
                                Owned(format!("{}{}", context.current_page_permalink, src)),
                                title,
                            )
                        );
                    }

                    Event::Start(Tag::Image(src, title))
                }
                Event::Start(Tag::Link(link, title)) => {
                    // A few situations here:
                    // - it could be a relative link (starting with `./`)
                    // - it could be a link to a co-located asset
                    // - it could be a normal link
                    // - any of those can be in a header or not: if it's in a header
                    //   we need to append to a string
                    let fixed_link = if link.starts_with("./") {
                        match resolve_internal_link(&link, context.permalinks) {
                            Ok(url) => url,
                            Err(_) => {
                                error = Some(format!("Relative link {} not found.", link).into());
                                return Event::Html(Borrowed(""));
                            }
                        }
                    } else if is_colocated_asset_link(&link) {
                        format!("{}{}", context.current_page_permalink, link)
                    } else {
                        if context.config.check_external_links
                            && !link.starts_with('#')
                            && !link.starts_with("mailto:") {
                            let res = check_url(&link);
                            if res.is_valid() {
                                link.to_string()
                            } else {
                                error = Some(
                                    format!("Link {} is not valid: {}", link, res.message()).into()
                                );
                                String::new()
                            }
                        } else {
                            link.to_string()
                        }
                    };

                    if in_header {
                        let html = if title.is_empty() {
                            format!("", fixed_link)
                        } else {
                            format!("", fixed_link, title)
                        };
                        temp_header.push(&html);
                        return Event::Html(Borrowed(""));
                    }

                    Event::Start(Tag::Link(Owned(fixed_link), title))
                }
                Event::End(Tag::Link(_, _)) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::Start(Tag::Code) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::End(Tag::Code) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::Start(Tag::Header(num)) => {
                    in_header = true;
                    temp_header = TempHeader::new(num);
                    Event::Html(Borrowed(""))
                }
                Event::End(Tag::Header(_)) => {
                    // End of a header, reset all the things and return the stringified
                    // version of the header
                    in_header = false;
                    header_created = false;
                    let val = temp_header.to_string(context.tera, context.insert_anchor);
                    headers.push(temp_header.clone());
                    temp_header = TempHeader::default();
                    Event::Html(Owned(val))
                }
                Event::Html(ref markup) if markup.contains("") => {
                    has_summary = true;
                    Event::Html(Borrowed(CONTINUE_READING))
                }
                _ => event,
            }
        });

        cmark::html::push_html(&mut html, parser);
    }

    if let Some(e) = error {
        return Err(e)
    } else {
        html = html.replace("

", "").replace("

", "
");
        Ok(Rendered {
            summary_len: if has_summary { html.find(CONTINUE_READING) } else { None },
            body: html,
            toc: make_table_of_contents(&headers)
        })
    }
}
<commit_msg>No replacein markdown rendering<commit_after>use std::borrow::Cow::{Owned, Borrowed};

use pulldown_cmark as cmark;
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
use slug::slugify;
use syntect::easy::HighlightLines;
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};

use errors::Result;
use utils::site::resolve_internal_link;
use highlighting::{get_highlighter, THEME_SET};
use link_checker::check_url;

use table_of_contents::{TempHeader, Header, make_table_of_contents};
use context::RenderContext;

const CONTINUE_READING: &str = "

\n";

#[derive(Debug)]
pub struct Rendered {
    pub body: String,
    pub summary_len: Option<usize>,
    pub toc: Vec
}

// We might have cases where the slug is already present in our list of anchor
// for example an article could have several titles named Example
// We add a counter after the slug if the slug is already present, which
// means we will have example, example-1, example-2 etc
fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
    if level == 0 && !anchors.contains(&name) {
        return name.to_string();
    }

    let new_anchor = format!("{}-{}", name, level + 1);
    if !anchors.contains(&new_anchor) {
        return new_anchor;
    }

    find_anchor(anchors, name, level + 1)
}

fn is_colocated_asset_link(link: &str) -> bool {
    !link.contains("/")  // http://, ftp://, ../ etc
        && !link.starts_with("mailto:")
}

pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Rendered> {
    // the rendered html
    let mut html = String::with_capacity(content.len());
    // Set while parsing
    let mut error = None;

    let mut background = IncludeBackground::Yes;
    let mut highlighter: Option<HighlightLines> = None;
    // If we get text in header, we need to insert the id and a anchor
    let mut in_header = false;
    // pulldown_cmark can send several text events for a title if there are markdown
    // specific characters like `!` in them. We only want to insert the anchor the first time
    let mut header_created = false;
    let mut anchors: Vec<String> = vec![];

    let mut headers = vec![];
    // Defaults to a 0 level so not a real header
    // It should be an Option ideally but not worth the hassle to update
    let mut temp_header = TempHeader::default();

    let mut opts = Options::empty();
    let mut has_summary = false;
    opts.insert(OPTION_ENABLE_TABLES);
    opts.insert(OPTION_ENABLE_FOOTNOTES);

    {
        let parser = Parser::new_ext(content, opts).map(|event| {
            match event {
                Event::Text(text) => {
                    // Header first
                    if in_header {
                        if header_created {
                            temp_header.push(&text);
                            return Event::Html(Borrowed(""));
                        }
                        let id = find_anchor(&anchors, slugify(&text), 0);
                        anchors.push(id.clone());
                        // update the header and add it to the list
                        temp_header.permalink = format!("{}#{}", context.current_page_permalink, id);
                        temp_header.id = id;
                        // += as we might have some  or other things already there
                        temp_header.title += &text;
                        header_created = true;
                        return Event::Html(Borrowed(""));
                    }

                    // if we are in the middle of a code block
                    if let Some(ref mut highlighter) = highlighter {
                        let highlighted = &highlighter.highlight(&text);
                        let html = styles_to_coloured_html(highlighted, background);
                        return Event::Html(Owned(html));
                    }

                    // Business as usual
                    Event::Text(text)
                }
                Event::Start(Tag::CodeBlock(ref info)) => {
                    if !context.config.highlight_code {
                        return Event::Html(Borrowed("
"));
                    }

                    let theme = &THEME_SET.themes[&context.config.highlight_theme];
                    match get_highlighter(&theme, info, context.base_path, &context.config.extra_syntaxes) {
                        Ok(h) => {
                            highlighter = Some(h);
                            // This selects the background color the same way that start_coloured_html_snippet does
                            let color = theme.settings.background.unwrap_or(::syntect::highlighting::Color::WHITE);
                            background = IncludeBackground::IfDifferent(color);
                        }
                        Err(err) => {
                            error = Some(format!("Could not load syntax: {}", err).into());
                            return Event::Html(Borrowed(""));
                        }
                    }
                    let snippet = start_coloured_html_snippet(theme);
                    Event::Html(Owned(snippet))
                }
                Event::End(Tag::CodeBlock(_)) => {
                    if !context.config.highlight_code {
                        return Event::Html(Borrowed("
\n"));
                    }
                    // reset highlight and close the code block
                    highlighter = None;
                    Event::Html(Borrowed("
"))
                }
                Event::Start(Tag::Image(src, title)) => {
                    if is_colocated_asset_link(&src) {
                        return Event::Start(
                            Tag::Image(
                                Owned(format!("{}{}", context.current_page_permalink, src)),
                                title,
                            )
                        );
                    }

                    Event::Start(Tag::Image(src, title))
                }
                Event::Start(Tag::Link(link, title)) => {
                    // A few situations here:
                    // - it could be a relative link (starting with `./`)
                    // - it could be a link to a co-located asset
                    // - it could be a normal link
                    // - any of those can be in a header or not: if it's in a header
                    //   we need to append to a string
                    let fixed_link = if link.starts_with("./") {
                        match resolve_internal_link(&link, context.permalinks) {
                            Ok(url) => url,
                            Err(_) => {
                                error = Some(format!("Relative link {} not found.", link).into());
                                return Event::Html(Borrowed(""));
                            }
                        }
                    } else if is_colocated_asset_link(&link) {
                        format!("{}{}", context.current_page_permalink, link)
                    } else {
                        if context.config.check_external_links
                            && !link.starts_with('#')
                            && !link.starts_with("mailto:") {
                            let res = check_url(&link);
                            if res.is_valid() {
                                link.to_string()
                            } else {
                                error = Some(
                                    format!("Link {} is not valid: {}", link, res.message()).into()
                                );
                                String::new()
                            }
                        } else {
                            link.to_string()
                        }
                    };

                    if in_header {
                        let html = if title.is_empty() {
                            format!("", fixed_link)
                        } else {
                            format!("", fixed_link, title)
                        };
                        temp_header.push(&html);
                        return Event::Html(Borrowed(""));
                    }

                    Event::Start(Tag::Link(Owned(fixed_link), title))
                }
                Event::End(Tag::Link(_, _)) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::Start(Tag::Code) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::End(Tag::Code) => {
                    if in_header {
                        temp_header.push("");
                        return Event::Html(Borrowed(""));
                    }
                    event
                }
                Event::Start(Tag::Header(num)) => {
                    in_header = true;
                    temp_header = TempHeader::new(num);
                    Event::Html(Borrowed(""))
                }
                Event::End(Tag::Header(_)) => {
                    // End of a header, reset all the things and return the stringified
                    // version of the header
                    in_header = false;
                    header_created = false;
                    let val = temp_header.to_string(context.tera, context.insert_anchor);
                    headers.push(temp_header.clone());
                    temp_header = TempHeader::default();
                    Event::Html(Owned(val))
                }
                Event::Html(ref markup) if markup.contains("") => {
                    has_summary = true;
                    Event::Html(Borrowed(CONTINUE_READING))
                }
                _ => event,
            }
        });

        cmark::html::push_html(&mut html, parser);
    }

    if let Some(e) = error {
        return Err(e)
    } else {
        Ok(Rendered {
            summary_len: if has_summary { html.find(CONTINUE_READING) } else { None },
            body: html,
            toc: make_table_of_contents(&headers)
        })
    }
}
<|endoftext|>