nu 0.113.0

A new type of shell
use nu_test_support::prelude::*;
use rstest::rstest;

use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;

#[rstest]
#[case::basic(r#""line1\nline2""#, "line1\nline2")]
#[case::basic(r#""col1\tcol2""#, "col1\tcol2")]
#[case::basic(r#""test\rmore""#, "test\rmore")]
#[case::basic(r#""path\\file""#, "path\\file")]
#[case::basic(r#""say \"hello\"""#, "say \"hello\"")]
#[case::basic(r#""it's \"quoted\"""#, "it's \"quoted\"")]
#[case::posix("\"x\\0y\"", "x\0y")]
#[case::posix("\"x\\ay\"", "x\u{7}y")]
#[case::posix("\"x\\by\"", "x\u{8}y")]
#[case::posix("\"x\\ey\"", "x\u{1b}y")]
#[case::posix("\"x\\fy\"", "x\u{c}y")]
#[case::posix("\"\\x41\\x42\\x43\"", "ABC")]
#[case::posix("\"hello\\x20world\"", "hello world")]
#[case::unicode(r#""\u{0041}""#, "A")]
#[case::unicode(r#""\u{1F600}""#, "😀")]
#[case::unicode(r#""\u{1234}""#, "ሴ")]
#[case::interpolation(
    r#"
        let name = "world"
        $"hello\n($name)"
    "#,
    "hello\nworld"
)]
#[case::interpolation(r#"$"tab\there""#, "tab\there")]
#[case::backward_compatible("\"line1\\nline2\"", "line1\nline2")]
#[case::backward_compatible("\"col1\\tcol2\"", "col1\tcol2")]
#[case::backward_compatible("\"path\\\\file\"", "path\\file")]
#[case::backward_compatible("\"say \\\"hello\\\"\"", "say \"hello\"")]
#[case::special_syntax_character("\"ignore\\$this\"", "ignore$this")]
#[case::special_syntax_character("\"not\\(expanded\\)\"", "not(expanded)")]
#[case::special_syntax_character("\"literal\\{curly\\}\"", "literal{curly}")]
fn escape_sequences_work(#[case] code: &str, #[case] expected: impl IntoValue) -> Result {
    test().run(code).expect_value_eq(expected)
}

#[rstest]
#[case(r#""hello \u{6e""#, "missing closing '}'")]
#[case(r#""\u{110000}""#, "max codepoint 0x10FFFF")]
#[case(
    r#""\u{000000000000000000000000000000000000000000000037}""#,
    "must be 1-6 hex digits"
)]
#[case(r#""\x4""#, "incomplete hex escape")]
#[case(r#""\x4z""#, "invalid hex escape")]
#[case(r#""\q""#, "unrecognized escape sequence")]
fn invalid_escape_sequences_report_parse_errors(
    #[case] code: &str,
    #[case] expected: &str,
) -> Result {
    let err = format!("{:?}", test().run(code).expect_parse_error()?);
    assert!(
        err.contains(expected),
        "expected parse error containing {expected:?}, got {err:?}"
    );
    Ok(())
}

#[test]
fn external_command_escape_sequences_work() -> Result {
    test()
        .add_nu_to_path()
        .run(
            r#"
                let quoted_text = "hello\nworld"
                nu --testbin cococo $quoted_text | str contains (char newline)
            "#,
        )
        .expect_value_eq(true)
}

#[test]
fn escaped_glob_pattern_reports_current_error() -> Result {
    Playground::setup(
        "escaped_glob_pattern_reports_current_error",
        |dirs, sandbox| {
            sandbox.with_files(&[
                FileWithContentToBeTrimmed("file[1].txt", "literal"),
                FileWithContentToBeTrimmed("file1.txt", "pattern"),
            ]);

            let mut tester = test().cwd(dirs.test());
            let err = match tester.run::<Value>(r#"ls file\[1\].txt"#) {
                Ok(value) => panic!("expected escaped glob path to fail, got {value:?}"),
                Err(err) => err.to_string(),
            };

            assert!(
                err.contains("unrecognized escape")
                    || (err.contains("No matches found for Expand(")
                        && err.contains("Pattern, file or folder not found"))
                    || (err.contains("Error extracting glob pattern")
                        && err.contains("invalid range pattern")),
                "expected escaped glob error, got {err:?}"
            );

            Ok(())
        },
    )
}

#[test]
fn quoted_glob_path_stays_literal() -> Result {
    Playground::setup("quoted_glob_path_stays_literal", |dirs, sandbox| {
        sandbox.with_files(&[
            FileWithContentToBeTrimmed("file[1].txt", "literal"),
            FileWithContentToBeTrimmed("file1.txt", "pattern"),
        ]);

        test()
            .cwd(dirs.test())
            .run(
                r#"
                    let quoted = "file[1].txt"
                    ls $quoted | get name | path basename
                "#,
            )
            .expect_value_eq(["file[1].txt"])
    })
}