<commit_before>use ra_syntax::{
    ast::{self, AstNode},
    SyntaxKind::{
        BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
        WHITESPACE,
    },
    SyntaxNode,
};
use stdx::format_to;
use test_utils::mark;

use crate::{AssistContext, AssistId, AssistKind, Assists};

// Assist: extract_variable
//
// Extracts subexpression into a variable.
//
// ```
// fn main() {
//     <|>(1 + 2)<|> * 4;
// }
// ```
// ->
// ```
// fn main() {
//     let $0var_name = (1 + 2);
//     var_name * 4;
// }
// ```
pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    if ctx.frange.range.is_empty() {
        return None;
    }
    let node = ctx.covering_element();
    if node.kind() == COMMENT {
        mark::hit!(extract_var_in_comment_is_not_applicable);
        return None;
    }
    let expr = node.ancestors().find_map(valid_target_expr)?;
    let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?;
    let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone();
    if indent.kind() != WHITESPACE {
        return None;
    }
    let target = expr.syntax().text_range();
    acc.add(
        AssistId("extract_variable", AssistKind::RefactorExtract),
        "Extract into variable",
        target,
        move |edit| {
            let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
                Some(field) => field.name_ref(),
                None => None,
            };

            let mut buf = String::new();

            let var_name = match &field_shorthand {
                Some(it) => it.to_string(),
                None => "var_name".to_string(),
            };
            let expr_range = match &field_shorthand {
                Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
                None => expr.syntax().text_range(),
            };

            if wrap_in_block {
                format_to!(buf, "{{ let {} = ", var_name);
            } else {
                format_to!(buf, "let {} = ", var_name);
            };
            format_to!(buf, "{}", expr.syntax());

            let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
            let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
                Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
            } else {
                false
            };
            if is_full_stmt {
                mark::hit!(test_extract_var_expr_stmt);
                if full_stmt.unwrap().semicolon_token().is_none() {
                    buf.push_str(";");
                }
                match ctx.config.snippet_cap {
                    Some(cap) => {
                        let snip = buf
                            .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
                        edit.replace_snippet(cap, expr_range, snip)
                    }
                    None => edit.replace(expr_range, buf),
                }
                return;
            }

            buf.push_str(";");

            // We want to maintain the indent level,
            // but we do not want to duplicate possible
            // extra newlines in the indent block
            let text = indent.text();
            if text.starts_with('\n') {
                buf.push_str("\n");
                buf.push_str(text.trim_start_matches('\n'));
            } else {
                buf.push_str(text);
            }

            edit.replace(expr_range, var_name.clone());
            let offset = anchor_stmt.text_range().start();
            match ctx.config.snippet_cap {
                Some(cap) => {
                    let snip =
                        buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
                    edit.insert_snippet(cap, offset, snip)
                }
                None => edit.insert(offset, buf),
            }

            if wrap_in_block {
                edit.insert(anchor_stmt.text_range().end(), " }");
            }
        },
    )
}

/// Check whether the node is a valid expression which can be extracted to a variable.
/// In general that's true for any expression, but in some cases that would produce invalid code.
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
    match node.kind() {
        PATH_EXPR | LOOP_EXPR => None,
        BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
        RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
        BLOCK_EXPR => {
            ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
        }
        _ => ast::Expr::cast(node),
    }
}

/// Returns the syntax node which will follow the freshly extractd var
/// and a boolean indicating whether we have to wrap it within a { } block
/// to produce correct code.
/// It can be a statement, the last in a block expression or a wanna be block
/// expression like a lambda or match arm.
fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
    expr.syntax().ancestors().find_map(|node| {
        if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
            if expr.syntax() == &node {
                mark::hit!(test_extract_var_last_expr);
                return Some((node, false));
            }
        }

        if let Some(parent) = node.parent() {
            if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
                return Some((node, true));
            }
        }

        if ast::Stmt::cast(node.clone()).is_some() {
            return Some((node, false));
        }

        None
    })
}

#[cfg(test)]
mod tests {
    use test_utils::mark;

    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

    use super::*;

    #[test]
    fn test_extract_var_simple() {
        check_assist(
            extract_variable,
            r#"
fn foo() {
    foo(<|>1 + 1<|>);
}"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
    foo(var_name);
}"#,
        );
    }

    #[test]
    fn extract_var_in_comment_is_not_applicable() {
        mark::check!(extract_var_in_comment_is_not_applicable);
        check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
    }

    #[test]
    fn test_extract_var_expr_stmt() {
        mark::check!(test_extract_var_expr_stmt);
        check_assist(
            extract_variable,
            r#"
fn foo() {
    <|>1 + 1<|>;
}"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
}"#,
        );
        check_assist(
            extract_variable,
            "
fn foo() {
    <|>{ let x = 0; x }<|>
    something_else();
}",
            "
fn foo() {
    let $0var_name = { let x = 0; x };
    something_else();
}",
        );
    }

    #[test]
    fn test_extract_var_part_of_expr_stmt() {
        check_assist(
            extract_variable,
            "
fn foo() {
    <|>1<|> + 1;
}",
            "
fn foo() {
    let $0var_name = 1;
    var_name + 1;
}",
        );
    }

    #[test]
    fn test_extract_var_last_expr() {
        mark::check!(test_extract_var_last_expr);
        check_assist(
            extract_variable,
            r#"
fn foo() {
    bar(<|>1 + 1<|>)
}
"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
    bar(var_name)
}
"#,
        );
        check_assist(
            extract_variable,
            r#"
fn foo() {
    <|>bar(1 + 1)<|>
}
"#,
            r#"
fn foo() {
    let $0var_name = bar(1 + 1);
    var_name
}
"#,
        )
    }

    #[test]
    fn test_extract_var_in_match_arm_no_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => (<|>2 + 2<|>, true)
        _ => (0, false)
    };
}
",
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => { let $0var_name = 2 + 2; (var_name, true) }
        _ => (0, false)
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_match_arm_with_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => {
            let y = 1;
            (<|>2 + y<|>, true)
        }
        _ => (0, false)
    };
}
",
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => {
            let y = 1;
            let $0var_name = 2 + y;
            (var_name, true)
        }
        _ => (0, false)
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_closure_no_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let lambda = |x: u32| <|>x * 2<|>;
}
",
            "
fn main() {
    let lambda = |x: u32| { let $0var_name = x * 2; var_name };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_closure_with_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let lambda = |x: u32| { <|>x * 2<|> };
}
",
            "
fn main() {
    let lambda = |x: u32| { let $0var_name = x * 2; var_name };
}
",
        );
    }

    #[test]
    fn test_extract_var_path_simple() {
        check_assist(
            extract_variable,
            "
fn main() {
    let o = <|>Some(true)<|>;
}
",
            "
fn main() {
    let $0var_name = Some(true);
    let o = var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_path_method() {
        check_assist(
            extract_variable,
            "
fn main() {
    let v = <|>bar.foo()<|>;
}
",
            "
fn main() {
    let $0var_name = bar.foo();
    let v = var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_return() {
        check_assist(
            extract_variable,
            "
fn foo() -> u32 {
    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {
    let $0var_name = 2 + 2;
    return var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_does_not_add_extra_whitespace() {
        check_assist(
            extract_variable,
            "
fn foo() -> u32 {

    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {

    let $0var_name = 2 + 2;
    return var_name;
}
",
        );

        check_assist(
            extract_variable,
            "
fn foo() -> u32 {

        <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {

        let $0var_name = 2 + 2;
        return var_name;
}
",
        );

        check_assist(
            extract_variable,
            "
fn foo() -> u32 {
    let foo = 1;

    // bar

    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {
    let foo = 1;

    // bar

    let $0var_name = 2 + 2;
    return var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_break() {
        check_assist(
            extract_variable,
            "
fn main() {
    let result = loop {
        <|>break 2 + 2<|>;
    };
}
",
            "
fn main() {
    let result = loop {
        let $0var_name = 2 + 2;
        break var_name;
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_for_cast() {
        check_assist(
            extract_variable,
            "
fn main() {
    let v = <|>0f32 as u32<|>;
}
",
            "
fn main() {
    let $0var_name = 0f32 as u32;
    let v = var_name;
}
",
        );
    }

    #[test]
    fn extract_var_field_shorthand() {
        check_assist(
            extract_variable,
            r#"
struct S {
    foo: i32
}

fn main() {
    S { foo: <|>1 + 1<|> }
}
"#,
            r#"
struct S {
    foo: i32
}

fn main() {
    let $0foo = 1 + 1;
    S { foo }
}
"#,
        )
    }

    #[test]
    fn test_extract_var_for_return_not_applicable() {
        check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
    }

    #[test]
    fn test_extract_var_for_break_not_applicable() {
        check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
    }

    // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
    #[test]
    fn extract_var_target() {
        check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");

        check_assist_target(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => (<|>2 + 2<|>, true)
        _ => (0, false)
    };
}
",
            "2 + 2",
        );
    }
}
<commit_msg>minor<commit_after>use ra_syntax::{
    ast::{self, AstNode},
    SyntaxKind::{
        BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
        WHITESPACE,
    },
    SyntaxNode,
};
use stdx::format_to;
use test_utils::mark;

use crate::{AssistContext, AssistId, AssistKind, Assists};

// Assist: extract_variable
//
// Extracts subexpression into a variable.
//
// ```
// fn main() {
//     <|>(1 + 2)<|> * 4;
// }
// ```
// ->
// ```
// fn main() {
//     let $0var_name = (1 + 2);
//     var_name * 4;
// }
// ```
pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    if ctx.frange.range.is_empty() {
        return None;
    }
    let node = ctx.covering_element();
    if node.kind() == COMMENT {
        mark::hit!(extract_var_in_comment_is_not_applicable);
        return None;
    }
    let expr = node.ancestors().find_map(valid_target_expr)?;
    let (anchor_stmt, wrap_in_block) = anchor_stmt(&expr)?;
    let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone();
    if indent.kind() != WHITESPACE {
        return None;
    }
    let target = expr.syntax().text_range();
    acc.add(
        AssistId("extract_variable", AssistKind::RefactorExtract),
        "Extract into variable",
        target,
        move |edit| {
            let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
                Some(field) => field.name_ref(),
                None => None,
            };

            let mut buf = String::new();

            let var_name = match &field_shorthand {
                Some(it) => it.to_string(),
                None => "var_name".to_string(),
            };
            let expr_range = match &field_shorthand {
                Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
                None => expr.syntax().text_range(),
            };

            if wrap_in_block {
                format_to!(buf, "{{ let {} = ", var_name);
            } else {
                format_to!(buf, "let {} = ", var_name);
            };
            format_to!(buf, "{}", expr.syntax());

            let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
            let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
                Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
            } else {
                false
            };
            if is_full_stmt {
                mark::hit!(test_extract_var_expr_stmt);
                if full_stmt.unwrap().semicolon_token().is_none() {
                    buf.push_str(";");
                }
                match ctx.config.snippet_cap {
                    Some(cap) => {
                        let snip = buf
                            .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
                        edit.replace_snippet(cap, expr_range, snip)
                    }
                    None => edit.replace(expr_range, buf),
                }
                return;
            }

            buf.push_str(";");

            // We want to maintain the indent level,
            // but we do not want to duplicate possible
            // extra newlines in the indent block
            let text = indent.text();
            if text.starts_with('\n') {
                buf.push_str("\n");
                buf.push_str(text.trim_start_matches('\n'));
            } else {
                buf.push_str(text);
            }

            edit.replace(expr_range, var_name.clone());
            let offset = anchor_stmt.text_range().start();
            match ctx.config.snippet_cap {
                Some(cap) => {
                    let snip =
                        buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
                    edit.insert_snippet(cap, offset, snip)
                }
                None => edit.insert(offset, buf),
            }

            if wrap_in_block {
                edit.insert(anchor_stmt.text_range().end(), " }");
            }
        },
    )
}

/// Check whether the node is a valid expression which can be extracted to a variable.
/// In general that's true for any expression, but in some cases that would produce invalid code.
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
    match node.kind() {
        PATH_EXPR | LOOP_EXPR => None,
        BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
        RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
        BLOCK_EXPR => {
            ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
        }
        _ => ast::Expr::cast(node),
    }
}

/// Returns the syntax node which will follow the freshly extractd var
/// and a boolean indicating whether we have to wrap it within a { } block
/// to produce correct code.
/// It can be a statement, the last in a block expression or a wanna be block
/// expression like a lambda or match arm.
fn anchor_stmt(expr: &ast::Expr) -> Option<(SyntaxNode, bool)> {
    expr.syntax().ancestors().find_map(|node| {
        if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
            if expr.syntax() == &node {
                mark::hit!(test_extract_var_last_expr);
                return Some((node, false));
            }
        }

        if let Some(parent) = node.parent() {
            if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
                return Some((node, true));
            }
        }

        if ast::Stmt::cast(node.clone()).is_some() {
            return Some((node, false));
        }

        None
    })
}

#[cfg(test)]
mod tests {
    use test_utils::mark;

    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

    use super::*;

    #[test]
    fn test_extract_var_simple() {
        check_assist(
            extract_variable,
            r#"
fn foo() {
    foo(<|>1 + 1<|>);
}"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
    foo(var_name);
}"#,
        );
    }

    #[test]
    fn extract_var_in_comment_is_not_applicable() {
        mark::check!(extract_var_in_comment_is_not_applicable);
        check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
    }

    #[test]
    fn test_extract_var_expr_stmt() {
        mark::check!(test_extract_var_expr_stmt);
        check_assist(
            extract_variable,
            r#"
fn foo() {
    <|>1 + 1<|>;
}"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
}"#,
        );
        check_assist(
            extract_variable,
            "
fn foo() {
    <|>{ let x = 0; x }<|>
    something_else();
}",
            "
fn foo() {
    let $0var_name = { let x = 0; x };
    something_else();
}",
        );
    }

    #[test]
    fn test_extract_var_part_of_expr_stmt() {
        check_assist(
            extract_variable,
            "
fn foo() {
    <|>1<|> + 1;
}",
            "
fn foo() {
    let $0var_name = 1;
    var_name + 1;
}",
        );
    }

    #[test]
    fn test_extract_var_last_expr() {
        mark::check!(test_extract_var_last_expr);
        check_assist(
            extract_variable,
            r#"
fn foo() {
    bar(<|>1 + 1<|>)
}
"#,
            r#"
fn foo() {
    let $0var_name = 1 + 1;
    bar(var_name)
}
"#,
        );
        check_assist(
            extract_variable,
            r#"
fn foo() {
    <|>bar(1 + 1)<|>
}
"#,
            r#"
fn foo() {
    let $0var_name = bar(1 + 1);
    var_name
}
"#,
        )
    }

    #[test]
    fn test_extract_var_in_match_arm_no_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => (<|>2 + 2<|>, true)
        _ => (0, false)
    };
}
",
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => { let $0var_name = 2 + 2; (var_name, true) }
        _ => (0, false)
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_match_arm_with_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => {
            let y = 1;
            (<|>2 + y<|>, true)
        }
        _ => (0, false)
    };
}
",
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => {
            let y = 1;
            let $0var_name = 2 + y;
            (var_name, true)
        }
        _ => (0, false)
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_closure_no_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let lambda = |x: u32| <|>x * 2<|>;
}
",
            "
fn main() {
    let lambda = |x: u32| { let $0var_name = x * 2; var_name };
}
",
        );
    }

    #[test]
    fn test_extract_var_in_closure_with_block() {
        check_assist(
            extract_variable,
            "
fn main() {
    let lambda = |x: u32| { <|>x * 2<|> };
}
",
            "
fn main() {
    let lambda = |x: u32| { let $0var_name = x * 2; var_name };
}
",
        );
    }

    #[test]
    fn test_extract_var_path_simple() {
        check_assist(
            extract_variable,
            "
fn main() {
    let o = <|>Some(true)<|>;
}
",
            "
fn main() {
    let $0var_name = Some(true);
    let o = var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_path_method() {
        check_assist(
            extract_variable,
            "
fn main() {
    let v = <|>bar.foo()<|>;
}
",
            "
fn main() {
    let $0var_name = bar.foo();
    let v = var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_return() {
        check_assist(
            extract_variable,
            "
fn foo() -> u32 {
    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {
    let $0var_name = 2 + 2;
    return var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_does_not_add_extra_whitespace() {
        check_assist(
            extract_variable,
            "
fn foo() -> u32 {

    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {

    let $0var_name = 2 + 2;
    return var_name;
}
",
        );

        check_assist(
            extract_variable,
            "
fn foo() -> u32 {

        <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {

        let $0var_name = 2 + 2;
        return var_name;
}
",
        );

        check_assist(
            extract_variable,
            "
fn foo() -> u32 {
    let foo = 1;

    // bar

    <|>return 2 + 2<|>;
}
",
            "
fn foo() -> u32 {
    let foo = 1;

    // bar

    let $0var_name = 2 + 2;
    return var_name;
}
",
        );
    }

    #[test]
    fn test_extract_var_break() {
        check_assist(
            extract_variable,
            "
fn main() {
    let result = loop {
        <|>break 2 + 2<|>;
    };
}
",
            "
fn main() {
    let result = loop {
        let $0var_name = 2 + 2;
        break var_name;
    };
}
",
        );
    }

    #[test]
    fn test_extract_var_for_cast() {
        check_assist(
            extract_variable,
            "
fn main() {
    let v = <|>0f32 as u32<|>;
}
",
            "
fn main() {
    let $0var_name = 0f32 as u32;
    let v = var_name;
}
",
        );
    }

    #[test]
    fn extract_var_field_shorthand() {
        check_assist(
            extract_variable,
            r#"
struct S {
    foo: i32
}

fn main() {
    S { foo: <|>1 + 1<|> }
}
"#,
            r#"
struct S {
    foo: i32
}

fn main() {
    let $0foo = 1 + 1;
    S { foo }
}
"#,
        )
    }

    #[test]
    fn test_extract_var_for_return_not_applicable() {
        check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
    }

    #[test]
    fn test_extract_var_for_break_not_applicable() {
        check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
    }

    // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
    #[test]
    fn extract_var_target() {
        check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");

        check_assist_target(
            extract_variable,
            "
fn main() {
    let x = true;
    let tuple = match x {
        true => (<|>2 + 2<|>, true)
        _ => (0, false)
    };
}
",
            "2 + 2",
        );
    }
}
<|endoftext|>


<commit_before><commit_msg>docs: Clarify mention of example elsewhere<commit_after><|endoftext|>


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

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

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

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

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

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

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

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

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

/// A macro to dispatch methods to builtin codecs
///
/// This avoids having to do a search over the codecs's specs for matching
/// `formats`.
macro_rules! dispatch_builtins {
    ($format:expr, $method:ident $(,$arg:expr)*) => {
        match $format {
            #[cfg(feature = "codec-date")]
            Format::Date => Some(codec_date::DateCodec::$method($($arg),*)),
            #[cfg(feature = "codec-docx")]
            Format::Docx => Some(codec_docx::DocxCodec::$method($($arg),*)),
            #[cfg(feature = "codec-html")]
            Format::Html => Some(codec_html::HtmlCodec::$method($($arg),*)),
            #[cfg(feature = "codec-ipynb")]
            Format::Ipynb => Some(codec_ipynb::IpynbCodec::$method($($arg),*)),
            #[cfg(feature = "codec-json")]
            Format::Json => Some(codec_json::JsonCodec::$method($($arg),*)),
            #[cfg(feature = "codec-json5")]
            Format::Json5 => Some(codec_json5::Json5Codec::$method($($arg),*)),
            #[cfg(feature = "codec-latex")]
            Format::LaTeX => Some(codec_latex::LatexCodec::$method($($arg),*)),
            #[cfg(feature = "codec-md")]
            Format::Markdown => Some(codec_md::MdCodec::$method($($arg),*)),
            #[cfg(feature = "codec-pandoc")]
            Format::Pandoc => Some(codec_pandoc::PandocCodec::$method($($arg),*)),
            #[cfg(feature = "codec-pdf")]
            Format::Pdf => Some(codec_pdf::PdfCodec::$method($($arg),*)),
            #[cfg(feature = "codec-person")]
            Format::Person => Some(codec_person::PersonCodec::$method($($arg),*)),
            #[cfg(feature = "codec-png")]
            Format::Png => Some(codec_png::PngCodec::$method($($arg),*)),
            #[cfg(feature = "codec-rmd")]
            Format::RMarkdown => Some(codec_rmd::RmdCodec::$method($($arg),*)),
            #[cfg(feature = "codec-rpng")]
            Format::Rpng => Some(codec_rpng::RpngCodec::$method($($arg),*)),
            #[cfg(feature = "codec-toml")]
            Format::Toml => Some(codec_toml::TomlCodec::$method($($arg),*)),
            #[cfg(feature = "codec-txt")]
            Format::PlainText => Some(codec_txt::TxtCodec::$method($($arg),*)),
            #[cfg(feature = "codec-yaml")]
            Format::Yaml => Some(codec_yaml::YamlCodec::$method($($arg),*)),

            _ => None
        }
    };
}

impl Codecs {
    /// Create a new codec registry
    ///
    /// Note that these strings are labels for the codec which
    /// aim to be consistent with the codec name, are convenient
    /// to use to `stencila codecs show`, and don't need to be
    /// consistent with format names or aliases.
    pub fn new() -> Self {
        let inner = vec![
            #[cfg(feature = "codec-date")]
            ("date", codec_date::DateCodec::spec()),
            #[cfg(feature = "codec-docx")]
            ("docx", codec_docx::DocxCodec::spec()),
            #[cfg(feature = "codec-html")]
            ("html", codec_html::HtmlCodec::spec()),
            #[cfg(feature = "codec-ipynb")]
            ("ipynb", codec_ipynb::IpynbCodec::spec()),
            #[cfg(feature = "codec-json")]
            ("json", codec_json::JsonCodec::spec()),
            #[cfg(feature = "codec-json5")]
            ("json5", codec_json5::Json5Codec::spec()),
            #[cfg(feature = "codec-latex")]
            ("latex", codec_latex::LatexCodec::spec()),
            #[cfg(feature = "codec-md")]
            ("md", codec_md::MdCodec::spec()),
            #[cfg(feature = "codec-pandoc")]
            ("pandoc", codec_pandoc::PandocCodec::spec()),
            #[cfg(feature = "codec-pdf")]
            ("pdf", codec_pdf::PdfCodec::spec()),
            #[cfg(feature = "codec-person")]
            ("person", codec_person::PersonCodec::spec()),
            #[cfg(feature = "codec-png")]
            ("png", codec_png::PngCodec::spec()),
            #[cfg(feature = "codec-rmd")]
            ("rmd", codec_rmd::RmdCodec::spec()),
            #[cfg(feature = "codec-rpng")]
            ("rpng", codec_rpng::RpngCodec::spec()),
            #[cfg(feature = "codec-toml")]
            ("toml", codec_toml::TomlCodec::spec()),
            #[cfg(feature = "codec-txt")]
            ("txt", codec_txt::TxtCodec::spec()),
            #[cfg(feature = "codec-yaml")]
            ("yaml", codec_yaml::YamlCodec::spec()),
        ]
        .into_iter()
        .map(|(label, codec)| (label.to_string(), codec))
        .collect();

        Self { inner }
    }

    /// List the available codecs
    fn list(&self) -> &BTreeMap<String, Codec> {
        &self.inner
    }

    /// Generate a Markdown table of the available codecs
    fn table(&self) -> String {
        let cols = "|-----|------|-------|----------|-------------------|";
        let head = "|Label|Status|Formats|Root types|Unsupported content";
        let body = self
            .inner
            .iter()
            .map(|(label, codec)| {
                format!(
                    "|{}|{}|{}|{}|{}|",
                    label,
                    codec.status,
                    codec.formats.join(", "),
                    if codec.root_types == vec!["*"] {
                        "*all*".to_string()
                    } else {
                        codec.root_types.join(", ")
                    },
                    codec.unsupported_types.join(", ")
                )
            })
            .collect::<Vec<String>>()
            .join("\n");
        format!(
            "{top}\n{head}\n{align}\n{body}\n{bottom}\n",
            top = cols,
            head = head,
            align = cols,
            body = body,
            bottom = cols
        )
    }

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

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

        let options = Some(DecodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

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

    /// Decode a document node from a file system path
    #[allow(clippy::needless_update)]
    async fn from_path(
        &self,
        path: &Path,
        format: &str,
        options: Option<DecodeOptions>,
    ) -> Result<Node> {
        let format = match_name(format);
        let format_spec = format.spec();

        let options = Some(DecodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

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

        let options = Some(EncodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

    /// Encode a document node to a file system path
    async fn to_path(
        &self,
        node: &Node,
        path: &Path,
        format: &str,
        options: Option<EncodeOptions>,
    ) -> Result<()> {
        let format = match_name(format);
        let format_spec = format.spec();

        let options = Some(EncodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

        bail!(
            "Unable to encode to path with format `{}`: no matching codec found",
            format_spec.title
        )
    }
}

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

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

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

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

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

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

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

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

    #[async_trait]
    impl Run for Show {
        async fn run(&self) -> Result {
            let codec = CODECS.get(&self.label)?;
            result::value(codec)
        }
    }
}
<commit_msg>refactor(Codecs): Make `format` arg optional in `from_path` and `to_path`<commit_after>use codec::{
    eyre::{bail, eyre, Result},
    stencila_schema::Node,
    Codec, CodecTrait,
};
use codec_format::FormatCodec;
use formats::{match_name, Format};
use once_cell::sync::Lazy;
use std::{collections::BTreeMap, path::Path, sync::Arc};

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

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

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

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

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

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

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

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

/// A macro to dispatch methods to builtin codecs
///
/// This avoids having to do a search over the codecs's specs for matching
/// `formats`.
macro_rules! dispatch_builtins {
    ($format:expr, $method:ident $(,$arg:expr)*) => {
        match $format {
            #[cfg(feature = "codec-date")]
            Format::Date => Some(codec_date::DateCodec::$method($($arg),*)),
            #[cfg(feature = "codec-docx")]
            Format::Docx => Some(codec_docx::DocxCodec::$method($($arg),*)),
            #[cfg(feature = "codec-html")]
            Format::Html => Some(codec_html::HtmlCodec::$method($($arg),*)),
            #[cfg(feature = "codec-ipynb")]
            Format::Ipynb => Some(codec_ipynb::IpynbCodec::$method($($arg),*)),
            #[cfg(feature = "codec-json")]
            Format::Json => Some(codec_json::JsonCodec::$method($($arg),*)),
            #[cfg(feature = "codec-json5")]
            Format::Json5 => Some(codec_json5::Json5Codec::$method($($arg),*)),
            #[cfg(feature = "codec-latex")]
            Format::LaTeX => Some(codec_latex::LatexCodec::$method($($arg),*)),
            #[cfg(feature = "codec-md")]
            Format::Markdown => Some(codec_md::MdCodec::$method($($arg),*)),
            #[cfg(feature = "codec-pandoc")]
            Format::Pandoc => Some(codec_pandoc::PandocCodec::$method($($arg),*)),
            #[cfg(feature = "codec-pdf")]
            Format::Pdf => Some(codec_pdf::PdfCodec::$method($($arg),*)),
            #[cfg(feature = "codec-person")]
            Format::Person => Some(codec_person::PersonCodec::$method($($arg),*)),
            #[cfg(feature = "codec-png")]
            Format::Png => Some(codec_png::PngCodec::$method($($arg),*)),
            #[cfg(feature = "codec-rmd")]
            Format::RMarkdown => Some(codec_rmd::RmdCodec::$method($($arg),*)),
            #[cfg(feature = "codec-rpng")]
            Format::Rpng => Some(codec_rpng::RpngCodec::$method($($arg),*)),
            #[cfg(feature = "codec-toml")]
            Format::Toml => Some(codec_toml::TomlCodec::$method($($arg),*)),
            #[cfg(feature = "codec-txt")]
            Format::PlainText => Some(codec_txt::TxtCodec::$method($($arg),*)),
            #[cfg(feature = "codec-yaml")]
            Format::Yaml => Some(codec_yaml::YamlCodec::$method($($arg),*)),

            _ => None
        }
    };
}

impl Codecs {
    /// Create a new codec registry
    ///
    /// Note that these strings are labels for the codec which
    /// aim to be consistent with the codec name, are convenient
    /// to use to `stencila codecs show`, and don't need to be
    /// consistent with format names or aliases.
    pub fn new() -> Self {
        let inner = vec![
            #[cfg(feature = "codec-date")]
            ("date", codec_date::DateCodec::spec()),
            #[cfg(feature = "codec-docx")]
            ("docx", codec_docx::DocxCodec::spec()),
            #[cfg(feature = "codec-html")]
            ("html", codec_html::HtmlCodec::spec()),
            #[cfg(feature = "codec-ipynb")]
            ("ipynb", codec_ipynb::IpynbCodec::spec()),
            #[cfg(feature = "codec-json")]
            ("json", codec_json::JsonCodec::spec()),
            #[cfg(feature = "codec-json5")]
            ("json5", codec_json5::Json5Codec::spec()),
            #[cfg(feature = "codec-latex")]
            ("latex", codec_latex::LatexCodec::spec()),
            #[cfg(feature = "codec-md")]
            ("md", codec_md::MdCodec::spec()),
            #[cfg(feature = "codec-pandoc")]
            ("pandoc", codec_pandoc::PandocCodec::spec()),
            #[cfg(feature = "codec-pdf")]
            ("pdf", codec_pdf::PdfCodec::spec()),
            #[cfg(feature = "codec-person")]
            ("person", codec_person::PersonCodec::spec()),
            #[cfg(feature = "codec-png")]
            ("png", codec_png::PngCodec::spec()),
            #[cfg(feature = "codec-rmd")]
            ("rmd", codec_rmd::RmdCodec::spec()),
            #[cfg(feature = "codec-rpng")]
            ("rpng", codec_rpng::RpngCodec::spec()),
            #[cfg(feature = "codec-toml")]
            ("toml", codec_toml::TomlCodec::spec()),
            #[cfg(feature = "codec-txt")]
            ("txt", codec_txt::TxtCodec::spec()),
            #[cfg(feature = "codec-yaml")]
            ("yaml", codec_yaml::YamlCodec::spec()),
        ]
        .into_iter()
        .map(|(label, codec)| (label.to_string(), codec))
        .collect();

        Self { inner }
    }

    /// List the available codecs
    fn list(&self) -> &BTreeMap<String, Codec> {
        &self.inner
    }

    /// Generate a Markdown table of the available codecs
    fn table(&self) -> String {
        let cols = "|-----|------|-------|----------|-------------------|";
        let head = "|Label|Status|Formats|Root types|Unsupported content";
        let body = self
            .inner
            .iter()
            .map(|(label, codec)| {
                format!(
                    "|{}|{}|{}|{}|{}|",
                    label,
                    codec.status,
                    codec.formats.join(", "),
                    if codec.root_types == vec!["*"] {
                        "*all*".to_string()
                    } else {
                        codec.root_types.join(", ")
                    },
                    codec.unsupported_types.join(", ")
                )
            })
            .collect::<Vec<String>>()
            .join("\n");
        format!(
            "{top}\n{head}\n{align}\n{body}\n{bottom}\n",
            top = cols,
            head = head,
            align = cols,
            body = body,
            bottom = cols
        )
    }

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

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

        let options = Some(DecodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

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

    /// Decode a document node from a file system path
    #[allow(clippy::needless_update)]
    async fn from_path(
        &self,
        path: &Path,
        format: Option<&str>,
        options: Option<DecodeOptions>,
    ) -> Result<Node> {
        let format = format
            .map(|str| str.to_string())
            .or_else(|| {
                path.extension()
                    .map(|os_str| os_str.to_string_lossy().into())
            })
            .ok_or_else(|| eyre!("No format supplied and path has no extension"))?;
        let format = match_name(&format);
        let format_spec = format.spec();

        let options = Some(DecodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

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

        let options = Some(EncodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

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

    /// Encode a document node to a file system path
    async fn to_path(
        &self,
        node: &Node,
        path: &Path,
        format: Option<&str>,
        options: Option<EncodeOptions>,
    ) -> Result<()> {
        let format = format
            .map(|str| str.to_string())
            .or_else(|| {
                path.extension()
                    .map(|os_str| os_str.to_string_lossy().into())
            })
            .ok_or_else(|| eyre!("No format supplied and path has no extension"))?;
        let format = match_name(&format);
        let format_spec = format.spec();

        let options = Some(EncodeOptions {
            format: Some(format_spec.extension.clone()),
            ..options.unwrap_or_default()
        });

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

        bail!(
            "Unable to encode to path with format `{}`: no matching codec found",
            format_spec.title
        )
    }
}

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

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

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

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

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

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

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

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

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


<commit_before>use serde::Deserialize;
use serde_json::{Result, Value};

#[derive(Deserialize)]
struct Versions {
    stable: Option<String>,
    bottle: bool,
}
#[derive(Deserialize)]
struct Bottle {
    stable: Option<BottleStable>,
}
#[derive(Deserialize)]
struct BottleStable {
    rebuild: u8,
    files: Value,
}
#[derive(Deserialize)]
struct Formula {
    name: String,
    versions: Versions,
    bottle: Bottle,
}

#[derive(Deserialize)]
struct Formulae(Vec<Formula>);

#[derive(Deserialize)]
struct BottleInfo {
    url: String,
    sha256: String,
}

fn main() -> Result<()> {
    let f: Formulae = serde_json::from_reader(std::io::stdin())?;
    for f in f.0 {
        if f.versions.bottle {
            let name = f.name;
            let ver = f.versions.stable.unwrap();
            let bs = f.bottle.stable.as_ref().unwrap();
            let rebuild = bs.rebuild;
            for (platform, v) in bs.files.as_object().unwrap().iter() {
                let v: BottleInfo = serde_json::from_value(v.clone()).unwrap();
                //a2ps-4.14.arm64_big_sur.bottle.4.tar.gz
                println!(
                    "{} {} bottles/{}-{}.{}.bottle.{}.tar.gz",
                    v.sha256, v.url, name, ver, platform, rebuild
                );
            }
        }
    }
    Ok(())
}
<commit_msg>homebrew-bottles: fix file name when rebuild == 0<commit_after>use serde::Deserialize;
use serde_json::{Result, Value};

#[derive(Deserialize)]
struct Versions {
    stable: Option<String>,
    bottle: bool,
}
#[derive(Deserialize)]
struct Bottle {
    stable: Option<BottleStable>,
}
#[derive(Deserialize)]
struct BottleStable {
    rebuild: u8,
    files: Value,
}
#[derive(Deserialize)]
struct Formula {
    name: String,
    versions: Versions,
    bottle: Bottle,
}

#[derive(Deserialize)]
struct Formulae(Vec<Formula>);

#[derive(Deserialize)]
struct BottleInfo {
    url: String,
    sha256: String,
}

fn main() -> Result<()> {
    let f: Formulae = serde_json::from_reader(std::io::stdin())?;
    for f in f.0 {
        if f.versions.bottle {
            let name = f.name;
            let ver = f.versions.stable.unwrap();
            let bs = f.bottle.stable.as_ref().unwrap();
            let rebuild = bs.rebuild;
            for (platform, v) in bs.files.as_object().unwrap().iter() {
                let v: BottleInfo = serde_json::from_value(v.clone()).unwrap();
                //a2ps-4.14.arm64_big_sur.bottle.4.tar.gz
                if rebuild == 0 {
                    println!(
                        "{} {} bottles/{}-{}.{}.bottle.tar.gz",
                        v.sha256, v.url, name, ver, platform
                    );
                } else {
                    println!(
                        "{} {} bottles/{}-{}.{}.bottle.{}.tar.gz",
                        v.sha256, v.url, name, ver, platform, rebuild
                    );
                }
            }
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;

#[rustfmt::skip]
fn main() {
    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "test_thrift crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env().expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        conf.base_path(base_path);

        let options = "serde";
        if !options.is_empty() {
            conf.options(options);
        }

        conf
    };

    conf
        .run(&[
            "../test_thrift.thrift"
        ])
        .expect("Failed while running thrift compilation");
}
<commit_msg>Support thrift_library::rust_include_srcs<commit_after>// @generated by autocargo
use std::env;
use std::fs;
use std::path::Path;

use thrift_compiler::Config;

#[rustfmt::skip]
fn main() {
    let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR env not provided");
    let out_dir: &Path = out_dir.as_ref();
    fs::write(
        out_dir.join("cratemap"),
        "test_thrift crate",
    ).expect("Failed to write cratemap");

    let conf = {
        let mut conf = Config::from_env().expect("Failed to instantiate thrift_compiler::Config");

        let path_from_manifest_to_base: &Path = "../../../../../..".as_ref();
        let cargo_manifest_dir =
            env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not provided");
        let cargo_manifest_dir: &Path = cargo_manifest_dir.as_ref();
        let base_path = cargo_manifest_dir
            .join(path_from_manifest_to_base)
            .canonicalize()
            .expect("Failed to canonicalize base_path");
        conf.base_path(base_path);

        let options = "serde";
        if !options.is_empty() {
            conf.options(options);
        }

        let include_srcs = vec![

        ];
        conf.include_srcs(include_srcs);

        conf
    };

    conf
        .run(&[
            "../test_thrift.thrift"
        ])
        .expect("Failed while running thrift compilation");
}
<|endoftext|>


<commit_before>pub mod function;
pub mod variables;
pub mod tables;
pub mod statements;

use std::vec::Vec;

use error;
use ast::lexer;
use ast::lexer::tokens;

use self::statements::Statement;

#[derive(PartialEq, Debug)]
pub enum Expression {
    Stub,
    Id(variables::Id),
    Assignment(Box<Expression>, Box<Expression>),
    Function {
        params: variables::Id,
        body: Expressions,
    },
    Indexing {
        object: Box<Expression>,
        index: Box<Expression>,
    },
    TableConstructor(Vec<Box<Expression>>),
    Binop(tokens::Keyword, Box<Expression>, Box<Expression>),
    Unop(tokens::Keyword, Box<Expression>),
    St(statements::Statement),
    String(String),
    Number(f64),
    Boolean(bool),
    Nil
}

pub type Expressions = Vec<Expression>;

// chunk ::= block

// block ::= {stat} [retstat]

// stat ::=  ‘;’ |
//     varlist ‘=’ explist |
//     functioncall |
//     label |
//     break |
//     goto Name |
//     do block end |
//     while exp do block end |
//     repeat block until exp |
//     if exp then block {elseif exp then block} [else block] end |
//     for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end |
//     for namelist in explist do block end |
//     function funcname funcbody |
//     local function Name funcbody |
//     local namelist [‘=’ explist]

// prefixexp ::= var | functioncall | ‘(’ exp ‘)’
pub fn parse_prefixexp(lexer: &mut lexer::Lexer) -> Result<Expression, error::Error> {
    lexer.try_to_parse(variables::parse_var)
        .or_else(|_| lexer.try_to_parse(function::parse_funcall))
        .or(Err(error::Error::new(lexer.head(), "Failed to parse prefix expression")))
}
// exp ::=  nil | false | true | Numeral | LiteralString | ‘...’ | functiondef |
//          prefixexp | tableconstructor | exp binop exp | unop exp
pub fn parse_exp(lexer: &mut lexer::Lexer) -> Result<Expression, error::Error> {
    println!("NEW PARSE {:?}", lexer);

    println!("0");
    // exp binop exp
    if let Some(mut sublexer) = lexer.take_while(|t| t.keyword().map_or(false, |k| k.is_binop())) {
        if let Ok(left) = parse_exp(&mut sublexer) {
            lexer.skip(sublexer.pos());

            if let tokens::TokenType::Keyword(binop) = lexer.head().token {
                lexer.skip(1);

                println!("PARSING EXP AFTER BINOP {:?} {:?} {:?}", left, binop, lexer.head());

                if let Ok(right) = lexer.try_to_parse(parse_exp) {
                    println!("FOUND RIGHT {:?}", right);
                    return Ok(Expression::Binop(binop, Box::new(left), Box::new(right)))
                }
            }
        }
    }

    println!("1");
    // unop exp
    if let tokens::TokenType::Keyword(keyword) = lexer.head().into() {
        if keyword.is_unop() {
            lexer.skip(1);

            if let Ok(exp) = lexer.try_to_parse(parse_exp) {
                return Ok(Expression::Unop(keyword, Box::new(exp)))
            }
        }
    }

    println!("2");
    // funcdef
    if let Ok(funcdef) = lexer.try_to_parse(function::parse_funcdef) {
        return Ok(funcdef)
    }

    println!("3");
    // prefixexp
    if let Ok(prefixexp) = lexer.try_to_parse(parse_prefixexp) {
        return Ok(prefixexp)
    }

    println!("4");
    // tableconstructor
    if let Ok(table) = lexer.try_to_parse(tables::parse_table_constructor) {
        return Ok(table)
    }

    println!("5");
    match lexer.head().token.clone() {
        tokens::TokenType::Keyword(tokens::Keyword::NIL) => Ok(Expression::Nil),
        tokens::TokenType::Keyword(tokens::Keyword::FALSE) => Ok(Expression::Boolean(false)),
        tokens::TokenType::Keyword(tokens::Keyword::TRUE) => Ok(Expression::Boolean(true)),
        tokens::TokenType::Keyword(tokens::Keyword::DOT3) => Ok(Expression::St(statements::Statement::Ellipsis)),
        tokens::TokenType::Number(number) => Ok(Expression::Number(number)),
        tokens::TokenType::String(string) => Ok(Expression::String(string)),
        _ => Err(error::Error::new(lexer.head(), "Unexpected token"))
    }.and_then(|x| { lexer.skip(1); Ok(x) })
}

impl Expression {
    pub fn from_lexer(lexer: &mut lexer::Lexer) -> Option<Expression> {
        match lexer.head().token {
            tokens::TokenType::Keyword(ref keyword) => {

                match keyword {
                    &tokens::Keyword::COLONS => Some(Expression::St(Statement::Break)),
                    &tokens::Keyword::FUNCTION => {
                        function::parse_funcdef(lexer).ok()
                    }
                    _ => panic!("Unexpected keyword: {:?}", keyword),
                }
            },
            tokens::TokenType::Id(_) => {
                variables::parse_var(lexer).ok()
            },
            tokens::TokenType::String(ref string) => {
                panic!("Unexpected Id: {}", string);
            },
            tokens::TokenType::Number(ref string) => {
                panic!("Unexpected Id: {}", string);
            },
            _ => panic!("Unexpected End of File")
        }
    }
}
<commit_msg>Remove debug statements<commit_after>pub mod function;
pub mod variables;
pub mod tables;
pub mod statements;

use std::vec::Vec;

use error;
use ast::lexer;
use ast::lexer::tokens;

use self::statements::Statement;

#[derive(PartialEq, Debug)]
pub enum Expression {
    Stub,
    Id(variables::Id),
    Assignment(Box<Expression>, Box<Expression>),
    Function {
        params: variables::Id,
        body: Expressions,
    },
    Indexing {
        object: Box<Expression>,
        index: Box<Expression>,
    },
    TableConstructor(Vec<Box<Expression>>),
    Binop(tokens::Keyword, Box<Expression>, Box<Expression>),
    Unop(tokens::Keyword, Box<Expression>),
    St(statements::Statement),
    String(String),
    Number(f64),
    Boolean(bool),
    Nil
}

pub type Expressions = Vec<Expression>;

// chunk ::= block

// block ::= {stat} [retstat]

// stat ::=  ‘;’ |
//     varlist ‘=’ explist |
//     functioncall |
//     label |
//     break |
//     goto Name |
//     do block end |
//     while exp do block end |
//     repeat block until exp |
//     if exp then block {elseif exp then block} [else block] end |
//     for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end |
//     for namelist in explist do block end |
//     function funcname funcbody |
//     local function Name funcbody |
//     local namelist [‘=’ explist]

// prefixexp ::= var | functioncall | ‘(’ exp ‘)’
pub fn parse_prefixexp(lexer: &mut lexer::Lexer) -> Result<Expression, error::Error> {
    lexer.try_to_parse(variables::parse_var)
        .or_else(|_| lexer.try_to_parse(function::parse_funcall))
        .or(Err(error::Error::new(lexer.head(), "Failed to parse prefix expression")))
}
// exp ::=  nil | false | true | Numeral | LiteralString | ‘...’ | functiondef |
//          prefixexp | tableconstructor | exp binop exp | unop exp
pub fn parse_exp(lexer: &mut lexer::Lexer) -> Result<Expression, error::Error> {
    // exp binop exp
    if let Some(mut sublexer) = lexer.take_while(|t| t.keyword().map_or(false, |k| k.is_binop())) {
        if let Ok(left) = parse_exp(&mut sublexer) {
            lexer.skip(sublexer.pos());

            if let tokens::TokenType::Keyword(binop) = lexer.head().token {
                lexer.skip(1);

                if let Ok(right) = lexer.try_to_parse(parse_exp) {
                    return Ok(Expression::Binop(binop, Box::new(left), Box::new(right)))
                }
            }
        }
    }

    // unop exp
    if let tokens::TokenType::Keyword(keyword) = lexer.head().into() {
        if keyword.is_unop() {
            lexer.skip(1);

            if let Ok(exp) = lexer.try_to_parse(parse_exp) {
                return Ok(Expression::Unop(keyword, Box::new(exp)))
            }
        }
    }

    // funcdef
    if let Ok(funcdef) = lexer.try_to_parse(function::parse_funcdef) {
        return Ok(funcdef)
    }

    // prefixexp
    if let Ok(prefixexp) = lexer.try_to_parse(parse_prefixexp) {
        return Ok(prefixexp)
    }

    // tableconstructor
    if let Ok(table) = lexer.try_to_parse(tables::parse_table_constructor) {
        return Ok(table)
    }

    match lexer.head().token.clone() {
        tokens::TokenType::Keyword(tokens::Keyword::NIL) => Ok(Expression::Nil),
        tokens::TokenType::Keyword(tokens::Keyword::FALSE) => Ok(Expression::Boolean(false)),
        tokens::TokenType::Keyword(tokens::Keyword::TRUE) => Ok(Expression::Boolean(true)),
        tokens::TokenType::Keyword(tokens::Keyword::DOT3) => Ok(Expression::St(statements::Statement::Ellipsis)),
        tokens::TokenType::Number(number) => Ok(Expression::Number(number)),
        tokens::TokenType::String(string) => Ok(Expression::String(string)),
        _ => Err(error::Error::new(lexer.head(), "Unexpected token"))
    }.and_then(|x| { lexer.skip(1); Ok(x) })
}

impl Expression {
    pub fn from_lexer(lexer: &mut lexer::Lexer) -> Option<Expression> {
        match lexer.head().token {
            tokens::TokenType::Keyword(ref keyword) => {

                match keyword {
                    &tokens::Keyword::COLONS => Some(Expression::St(Statement::Break)),
                    &tokens::Keyword::FUNCTION => {
                        function::parse_funcdef(lexer).ok()
                    }
                    _ => panic!("Unexpected keyword: {:?}", keyword),
                }
            },
            tokens::TokenType::Id(_) => {
                variables::parse_var(lexer).ok()
            },
            tokens::TokenType::String(ref string) => {
                panic!("Unexpected Id: {}", string);
            },
            tokens::TokenType::Number(ref string) => {
                panic!("Unexpected Id: {}", string);
            },
            _ => panic!("Unexpected End of File")
        }
    }
}
<|endoftext|>


<commit_before>//! C-compiler probing and detection.
//!
//! This module will fill out the `cc` and `cxx` maps of `Build` by looking for
//! C and C++ compilers for each target configured. A compiler is found through
//! a number of vectors (in order of precedence)
//!
//! 1. Configuration via `target.$target.cc` in `config.toml`.
//! 2. Configuration via `target.$target.android-ndk` in `config.toml`, if
//!    applicable
//! 3. Special logic to probe on OpenBSD
//! 4. The `CC_$target` environment variable.
//! 5. The `CC` environment variable.
//! 6. "cc"
//!
//! Some of this logic is implemented here, but much of it is farmed out to the
//! `cc` crate itself, so we end up having the same fallbacks as there.
//! Similar logic is then used to find a C++ compiler, just some s/cc/c++/ is
//! used.
//!
//! It is intended that after this module has run no C/C++ compiler will
//! ever be probed for. Instead the compilers found here will be used for
//! everything.

use std::collections::HashSet;
use std::{env, iter};
use std::path::{Path, PathBuf};
use std::process::Command;

use build_helper::output;

use crate::{Build, GitRepo};
use crate::config::Target;
use crate::cache::Interned;

// The `cc` crate doesn't provide a way to obtain a path to the detected archiver,
// so use some simplified logic here. First we respect the environment variable `AR`, then
// try to infer the archiver path from the C compiler path.
// In the future this logic should be replaced by calling into the `cc` crate.
fn cc2ar(cc: &Path, target: &str) -> Option<PathBuf> {
    if let Some(ar) = env::var_os("AR") {
        Some(PathBuf::from(ar))
    } else if target.contains("msvc") {
        None
    } else if target.contains("musl") {
        Some(PathBuf::from("ar"))
    } else if target.contains("openbsd") {
        Some(PathBuf::from("ar"))
    } else {
        let parent = cc.parent().unwrap();
        let file = cc.file_name().unwrap().to_str().unwrap();
        for suffix in &["gcc", "cc", "clang"] {
            if let Some(idx) = file.rfind(suffix) {
                let mut file = file[..idx].to_owned();
                file.push_str("ar");
                return Some(parent.join(&file));
            }
        }
        Some(parent.join(file))
    }
}

pub fn find(build: &mut Build) {
    // For all targets we're going to need a C compiler for building some shims
    // and such as well as for being a linker for Rust code.
    let targets = build.targets.iter().chain(&build.hosts).cloned().chain(iter::once(build.build))
                               .collect::<HashSet<_>>();
    for target in targets.into_iter() {
        let mut cfg = cc::Build::new();
        cfg.cargo_metadata(false).opt_level(2).warnings(false).debug(false)
           .target(&target).host(&build.build);
        match build.crt_static(target) {
            Some(a) => { cfg.static_crt(a); }
            None => {
                if target.contains("msvc") {
                    cfg.static_crt(true);
                }
                if target.contains("musl") {
                    cfg.static_flag(true);
                }
            }
        }

        let config = build.config.target_config.get(&target);
        if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
            cfg.compiler(cc);
        } else {
            set_compiler(&mut cfg, Language::C, target, config, build);
        }

        let compiler = cfg.get_compiler();
        let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
            ar
        } else {
            cc2ar(compiler.path(), &target)
        };

        build.cc.insert(target, compiler);
        let cflags = build.cflags(target, GitRepo::Rustc);

        // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
        // We'll need one anyways if the target triple is also a host triple
        cfg.cpp(true);
        if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
            cfg.compiler(cxx);
        } else {
            set_compiler(&mut cfg, Language::CPlusPlus, target, config, build);
        }
        let compiler = cfg.get_compiler();
        build.cxx.insert(target, compiler);

        build.verbose(&format!("CC_{} = {:?}", &target, build.cc(target)));
        build.verbose(&format!("CFLAGS_{} = {:?}", &target, cflags));
        build.verbose(&format!("CXX_{} = {:?}", &target, build.cxx(target)));
        build.verbose(&format!("CXXFLAGS_{} = {:?}", &target, cflags));
        if let Some(ar) = ar {
            build.verbose(&format!("AR_{} = {:?}", &target, ar));
            build.ar.insert(target, ar);
        }
    }
}

fn set_compiler(cfg: &mut cc::Build,
                compiler: Language,
                target: Interned<String>,
                config: Option<&Target>,
                build: &Build) {
    match &*target {
        // When compiling for android we may have the NDK configured in the
        // config.toml in which case we look there. Otherwise the default
        // compiler already takes into account the triple in question.
        t if t.contains("android") => {
            if let Some(ndk) = config.and_then(|c| c.ndk.as_ref()) {
                let target = target.replace("armv7neon", "arm")
                                   .replace("armv7", "arm")
                                   .replace("thumbv7neon", "arm")
                                   .replace("thumbv7", "arm");
                let compiler = format!("{}-{}", target, compiler.clang());
                cfg.compiler(ndk.join("bin").join(compiler));
            }
        }

        // The default gcc version from OpenBSD may be too old, try using egcc,
        // which is a gcc version from ports, if this is the case.
        t if t.contains("openbsd") => {
            let c = cfg.get_compiler();
            let gnu_compiler = compiler.gcc();
            if !c.path().ends_with(gnu_compiler) {
                return
            }

            let output = output(c.to_command().arg("--version"));
            let i = match output.find(" 4.") {
                Some(i) => i,
                None => return,
            };
            match output[i + 3..].chars().next().unwrap() {
                '0' ..= '6' => {}
                _ => return,
            }
            let alternative = format!("e{}", gnu_compiler);
            if Command::new(&alternative).output().is_ok() {
                cfg.compiler(alternative);
            }
        }

        "mips-unknown-linux-musl" => {
            if cfg.get_compiler().path().to_str() == Some("gcc") {
                cfg.compiler("mips-linux-musl-gcc");
            }
        }
        "mipsel-unknown-linux-musl" => {
            if cfg.get_compiler().path().to_str() == Some("gcc") {
                cfg.compiler("mipsel-linux-musl-gcc");
            }
        }

        t if t.contains("musl") => {
            if let Some(root) = build.musl_root(target) {
                let guess = root.join("bin/musl-gcc");
                if guess.exists() {
                    cfg.compiler(guess);
                }
            }
        }

        _ => {}
    }
}

/// The target programming language for a native compiler.
enum Language {
    /// The compiler is targeting C.
    C,
    /// The compiler is targeting C++.
    CPlusPlus,
}

impl Language {
    /// Obtains the name of a compiler in the GCC collection.
    fn gcc(self) -> &'static str {
        match self {
            Language::C => "gcc",
            Language::CPlusPlus => "g++",
        }
    }

    /// Obtains the name of a compiler in the clang suite.
    fn clang(self) -> &'static str {
        match self {
            Language::C => "clang",
            Language::CPlusPlus => "clang++",
        }
    }
}
<commit_msg>rustbuild: fix default value for cxx<commit_after>//! C-compiler probing and detection.
//!
//! This module will fill out the `cc` and `cxx` maps of `Build` by looking for
//! C and C++ compilers for each target configured. A compiler is found through
//! a number of vectors (in order of precedence)
//!
//! 1. Configuration via `target.$target.cc` in `config.toml`.
//! 2. Configuration via `target.$target.android-ndk` in `config.toml`, if
//!    applicable
//! 3. Special logic to probe on OpenBSD
//! 4. The `CC_$target` environment variable.
//! 5. The `CC` environment variable.
//! 6. "cc"
//!
//! Some of this logic is implemented here, but much of it is farmed out to the
//! `cc` crate itself, so we end up having the same fallbacks as there.
//! Similar logic is then used to find a C++ compiler, just some s/cc/c++/ is
//! used.
//!
//! It is intended that after this module has run no C/C++ compiler will
//! ever be probed for. Instead the compilers found here will be used for
//! everything.

use std::collections::HashSet;
use std::{env, iter};
use std::path::{Path, PathBuf};
use std::process::Command;

use build_helper::output;

use crate::{Build, GitRepo};
use crate::config::Target;
use crate::cache::Interned;

// The `cc` crate doesn't provide a way to obtain a path to the detected archiver,
// so use some simplified logic here. First we respect the environment variable `AR`, then
// try to infer the archiver path from the C compiler path.
// In the future this logic should be replaced by calling into the `cc` crate.
fn cc2ar(cc: &Path, target: &str) -> Option<PathBuf> {
    if let Some(ar) = env::var_os("AR") {
        Some(PathBuf::from(ar))
    } else if target.contains("msvc") {
        None
    } else if target.contains("musl") {
        Some(PathBuf::from("ar"))
    } else if target.contains("openbsd") {
        Some(PathBuf::from("ar"))
    } else {
        let parent = cc.parent().unwrap();
        let file = cc.file_name().unwrap().to_str().unwrap();
        for suffix in &["gcc", "cc", "clang"] {
            if let Some(idx) = file.rfind(suffix) {
                let mut file = file[..idx].to_owned();
                file.push_str("ar");
                return Some(parent.join(&file));
            }
        }
        Some(parent.join(file))
    }
}

pub fn find(build: &mut Build) {
    // For all targets we're going to need a C compiler for building some shims
    // and such as well as for being a linker for Rust code.
    let targets = build.targets.iter().chain(&build.hosts).cloned().chain(iter::once(build.build))
                               .collect::<HashSet<_>>();
    for target in targets.into_iter() {
        let mut cfg = cc::Build::new();
        cfg.cargo_metadata(false).opt_level(2).warnings(false).debug(false)
           .target(&target).host(&build.build);
        match build.crt_static(target) {
            Some(a) => { cfg.static_crt(a); }
            None => {
                if target.contains("msvc") {
                    cfg.static_crt(true);
                }
                if target.contains("musl") {
                    cfg.static_flag(true);
                }
            }
        }

        let config = build.config.target_config.get(&target);
        if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
            cfg.compiler(cc);
        } else {
            set_compiler(&mut cfg, Language::C, target, config, build);
        }

        let compiler = cfg.get_compiler();
        let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
            ar
        } else {
            cc2ar(compiler.path(), &target)
        };

        build.cc.insert(target, compiler);
        let cflags = build.cflags(target, GitRepo::Rustc);

        // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
        // We'll need one anyways if the target triple is also a host triple
        let mut cfg = cc::Build::new();
        cfg.cargo_metadata(false).opt_level(2).warnings(false).debug(false).cpp(true)
            .target(&target).host(&build.build);
        if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
            cfg.compiler(cxx);
        } else {
            set_compiler(&mut cfg, Language::CPlusPlus, target, config, build);
        }
        let compiler = cfg.get_compiler();
        build.cxx.insert(target, compiler);

        build.verbose(&format!("CC_{} = {:?}", &target, build.cc(target)));
        build.verbose(&format!("CFLAGS_{} = {:?}", &target, cflags));
        build.verbose(&format!("CXX_{} = {:?}", &target, build.cxx(target)));
        build.verbose(&format!("CXXFLAGS_{} = {:?}", &target, cflags));
        if let Some(ar) = ar {
            build.verbose(&format!("AR_{} = {:?}", &target, ar));
            build.ar.insert(target, ar);
        }
    }
}

fn set_compiler(cfg: &mut cc::Build,
                compiler: Language,
                target: Interned<String>,
                config: Option<&Target>,
                build: &Build) {
    match &*target {
        // When compiling for android we may have the NDK configured in the
        // config.toml in which case we look there. Otherwise the default
        // compiler already takes into account the triple in question.
        t if t.contains("android") => {
            if let Some(ndk) = config.and_then(|c| c.ndk.as_ref()) {
                let target = target.replace("armv7neon", "arm")
                                   .replace("armv7", "arm")
                                   .replace("thumbv7neon", "arm")
                                   .replace("thumbv7", "arm");
                let compiler = format!("{}-{}", target, compiler.clang());
                cfg.compiler(ndk.join("bin").join(compiler));
            }
        }

        // The default gcc version from OpenBSD may be too old, try using egcc,
        // which is a gcc version from ports, if this is the case.
        t if t.contains("openbsd") => {
            let c = cfg.get_compiler();
            let gnu_compiler = compiler.gcc();
            if !c.path().ends_with(gnu_compiler) {
                return
            }

            let output = output(c.to_command().arg("--version"));
            let i = match output.find(" 4.") {
                Some(i) => i,
                None => return,
            };
            match output[i + 3..].chars().next().unwrap() {
                '0' ..= '6' => {}
                _ => return,
            }
            let alternative = format!("e{}", gnu_compiler);
            if Command::new(&alternative).output().is_ok() {
                cfg.compiler(alternative);
            }
        }

        "mips-unknown-linux-musl" => {
            if cfg.get_compiler().path().to_str() == Some("gcc") {
                cfg.compiler("mips-linux-musl-gcc");
            }
        }
        "mipsel-unknown-linux-musl" => {
            if cfg.get_compiler().path().to_str() == Some("gcc") {
                cfg.compiler("mipsel-linux-musl-gcc");
            }
        }

        t if t.contains("musl") => {
            if let Some(root) = build.musl_root(target) {
                let guess = root.join("bin/musl-gcc");
                if guess.exists() {
                    cfg.compiler(guess);
                }
            }
        }

        _ => {}
    }
}

/// The target programming language for a native compiler.
enum Language {
    /// The compiler is targeting C.
    C,
    /// The compiler is targeting C++.
    CPlusPlus,
}

impl Language {
    /// Obtains the name of a compiler in the GCC collection.
    fn gcc(self) -> &'static str {
        match self {
            Language::C => "gcc",
            Language::CPlusPlus => "g++",
        }
    }

    /// Obtains the name of a compiler in the clang suite.
    fn clang(self) -> &'static str {
        match self {
            Language::C => "clang",
            Language::CPlusPlus => "clang++",
        }
    }
}
<|endoftext|>