use super::*;

#[test]
fn test_os_arch_functions_in_interpolation() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{arch()}} {{os()}} {{os_family()}} {{num_cpus()}}
      ",
    )
    .stdout(
      format!(
        "{} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .stderr(
      format!(
        "echo {} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .success();
}

#[test]
fn test_os_arch_functions_in_expression() {
  Test::new()
    .justfile(
      "
        a := arch()
        o := os()
        f := os_family()
        n := num_cpus()

        foo:
          echo {{a}} {{o}} {{f}} {{n}}
      ",
    )
    .stdout(
      format!(
        "{} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .stderr(
      format!(
        "echo {} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .success();
}

#[test]
fn env_var_functions_unix() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        p := env_var('USER')
        b := env_var_or_default('ZADDY', 'HTAP')
        x := env_var_or_default('XYZ', 'ABC')

        foo:
          /usr/bin/env echo '{{p}}' '{{b}}' '{{x}}'
      ",
    )
    .stdout(format!("{} HTAP ABC\n", env::var("USER").unwrap()).as_str())
    .stderr(
      format!(
        "/usr/bin/env echo '{}' 'HTAP' 'ABC'\n",
        env::var("USER").unwrap()
      )
      .as_str(),
    )
    .success();
}

#[test]
fn path_functions() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := without_extension('/foo/bar/baz.hello')
        fs  := file_stem('/foo/bar/baz.hello')
        fn  := file_name('/foo/bar/baz.hello')
        dir := parent_directory('/foo/bar/baz.hello')
        ext := extension('/foo/bar/baz.hello')
        jn  := join('a', 'b')

        foo:
          /usr/bin/env echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}' '{{jn}}'
      ",
    )
    .stdout("/foo/bar/baz baz baz.hello /foo/bar hello a/b\n")
    .stderr("/usr/bin/env echo '/foo/bar/baz' 'baz' 'baz.hello' '/foo/bar' 'hello' 'a/b'\n")
    .success();
}

#[test]
fn path_functions2() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := without_extension('/foo/bar/baz')
        fs  := file_stem('/foo/bar/baz.hello.ciao')
        fn  := file_name('/bar/baz.hello.ciao')
        dir := parent_directory('/foo/')
        ext := extension('/foo/bar/baz.hello.ciao')

        foo:
          /usr/bin/env echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}'
      ",
    )
    .stdout("/foo/bar/baz baz.hello baz.hello.ciao / ciao\n")
    .stderr("/usr/bin/env echo '/foo/bar/baz' 'baz.hello' 'baz.hello.ciao' '/' 'ciao'\n")
    .success();
}

#[test]
fn broken_without_extension_function() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := without_extension('')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `without_extension` failed: could not extract parent from ``
         ——▶ justfile:1:8
          │
        1 │ we  := without_extension('')
          │        ^^^^^^^^^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_extension_function() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := extension('')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `extension` failed: could not extract extension from ``
         ——▶ justfile:1:8
          │
        1 │ we  := extension('')
          │        ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_extension_function2() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := extension('foo')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `extension` failed: could not extract extension from `foo`
         ——▶ justfile:1:8
          │
        1 │ we  := extension('foo')
          │        ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_file_stem_function() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := file_stem('')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `file_stem` failed: could not extract file stem from ``
         ——▶ justfile:1:8
          │
        1 │ we  := file_stem('')
          │        ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_file_name_function() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := file_name('')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `file_name` failed: could not extract file name from ``
         ——▶ justfile:1:8
          │
        1 │ we  := file_name('')
          │        ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_directory_function() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := parent_directory('')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `parent_directory` failed: could not extract parent directory from ``
         ——▶ justfile:1:8
          │
        1 │ we  := parent_directory('')
          │        ^^^^^^^^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn broken_directory_function2() {
  if cfg!(windows) {
    return;
  }
  Test::new()
    .justfile(
      "
        we  := parent_directory('/')

        foo:
          /usr/bin/env echo '{{we}}'
      ",
    )
    .stderr(
      "
        error: call to function `parent_directory` failed: could not extract parent directory from `/`
         ——▶ justfile:1:8
          │
        1 │ we  := parent_directory('/')
          │        ^^^^^^^^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn parent_directory_of_bare_filename() {
  Test::new()
    .justfile(
      "
        foo:
          @echo {{parent_directory('foo')}}
      ",
    )
    .stdout(".\n")
    .success();
}

#[test]
fn env_var_functions_windows() {
  if cfg!(not(windows)) {
    return;
  }
  Test::new()
    .justfile(
      "
        p := env_var('USERNAME')
        b := env_var_or_default('ZADDY', 'HTAP')
        x := env_var_or_default('XYZ', 'ABC')

        foo:
          /usr/bin/env echo '{{p}}' '{{b}}' '{{x}}'
      ",
    )
    .stdout(format!("{} HTAP ABC\n", env::var("USERNAME").unwrap()).as_str())
    .stderr(
      format!(
        "/usr/bin/env echo '{}' 'HTAP' 'ABC'\n",
        env::var("USERNAME").unwrap()
      )
      .as_str(),
    )
    .success();
}

#[test]
fn env_var_failure() {
  Test::new()
    .arg("a")
    .justfile(
      "
        a:
          echo {{env_var('ZADDY')}}
      ",
    )
    .stderr(
      "
        error: call to function `env_var` failed: environment variable `ZADDY` not present
         ——▶ justfile:2:10
          │
        2 │   echo {{env_var('ZADDY')}}
          │          ^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn test_just_executable_function() {
  Test::new()
    .arg("a")
    .justfile(
      "
        a:
          @printf 'Executable path is: %s\\n' '{{ just_executable() }}'
      ",
    )
    .stdout(format!("Executable path is: {JUST}\n"))
    .success();
}

#[test]
fn test_os_arch_functions_in_default() {
  Test::new()
    .justfile(
      "
        foo a=arch() o=os() f=os_family() n=num_cpus():
          echo {{a}} {{o}} {{f}} {{n}}
      ",
    )
    .stdout(
      format!(
        "{} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .stderr(
      format!(
        "echo {} {} {} {}\n",
        env::consts::ARCH,
        env::consts::OS,
        env::consts::FAMILY,
        num_cpus::get()
      )
      .as_str(),
    )
    .success();
}

#[test]
fn clean() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ clean('a/../b') }}
      ",
    )
    .stdout("b\n")
    .stderr("echo b\n")
    .success();
}

#[test]
fn uppercase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ uppercase('bar') }}
      ",
    )
    .stdout("BAR\n")
    .stderr("echo BAR\n")
    .success();
}

#[test]
fn lowercase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ lowercase('BAR') }}
      ",
    )
    .stdout("bar\n")
    .stderr("echo bar\n")
    .success();
}

#[test]
fn uppercamelcase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ uppercamelcase('foo bar') }}
      ",
    )
    .stdout("FooBar\n")
    .stderr("echo FooBar\n")
    .success();
}

#[test]
fn lowercamelcase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ lowercamelcase('foo bar') }}
      ",
    )
    .stdout("fooBar\n")
    .stderr("echo fooBar\n")
    .success();
}

#[test]
fn snakecase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ snakecase('foo bar') }}
      ",
    )
    .stdout("foo_bar\n")
    .stderr("echo foo_bar\n")
    .success();
}

#[test]
fn kebabcase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ kebabcase('foo bar') }}
      ",
    )
    .stdout("foo-bar\n")
    .stderr("echo foo-bar\n")
    .success();
}

#[test]
fn shoutysnakecase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ shoutysnakecase('foo bar') }}
      ",
    )
    .stdout("FOO_BAR\n")
    .stderr("echo FOO_BAR\n")
    .success();
}

#[test]
fn titlecase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ titlecase('foo bar') }}
      ",
    )
    .stdout("Foo Bar\n")
    .stderr("echo Foo Bar\n")
    .success();
}

#[test]
fn shoutykebabcase() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ shoutykebabcase('foo bar') }}
      ",
    )
    .stdout("FOO-BAR\n")
    .stderr("echo FOO-BAR\n")
    .success();
}

#[test]
fn trim() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ trim('   bar   ') }}
      ",
    )
    .stdout("bar\n")
    .stderr("echo bar\n")
    .success();
}

#[test]
fn replace() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ replace('barbarbar', 'bar', 'foo') }}
      ",
    )
    .stdout("foofoofoo\n")
    .stderr("echo foofoofoo\n")
    .success();
}

#[test]
fn replace_regex() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ replace_regex('123bar123bar123bar', '\\d+bar', 'foo') }}
      ",
    )
    .stdout("foofoofoo\n")
    .stderr("echo foofoofoo\n")
    .success();
}

#[test]
fn invalid_replace_regex() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
      ",
    )
    .stderr(
      "
        error: call to function `replace_regex` failed: regex parse error:
            foo\\
               ^
        error: incomplete escape sequence, reached end of pattern prematurely
         ——▶ justfile:2:11
          │
        2 │   echo {{ replace_regex('barbarbar', 'foo\\', 'foo') }}
          │           ^^^^^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn capitalize() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ capitalize('BAR') }}
      ",
    )
    .stdout("Bar\n")
    .stderr("echo Bar\n")
    .success();
}

#[test]
fn semver_matches() {
  Test::new()
    .justfile(
      "
        foo:
          echo {{ semver_matches('0.1.0', '>=0.1.0') }}
          echo {{ semver_matches('0.1.0', '=0.0.1') }}
      ",
    )
    .stdout("true\nfalse\n")
    .stderr("echo true\necho false\n")
    .success();
}

#[test]
fn trim_end_matches() {
  assert_eval_eq("trim_end_matches('foo', 'o')", "f");
  assert_eval_eq("trim_end_matches('fabab', 'ab')", "f");
  assert_eval_eq("trim_end_matches('fbaabab', 'ab')", "fba");
}

#[test]
fn trim_end_match() {
  assert_eval_eq("trim_end_match('foo', 'o')", "fo");
  assert_eval_eq("trim_end_match('fabab', 'ab')", "fab");
}

#[test]
fn trim_start_matches() {
  assert_eval_eq("trim_start_matches('oof', 'o')", "f");
  assert_eval_eq("trim_start_matches('ababf', 'ab')", "f");
  assert_eval_eq("trim_start_matches('ababbaf', 'ab')", "baf");
}

#[test]
fn trim_start_match() {
  assert_eval_eq("trim_start_match('oof', 'o')", "of");
  assert_eval_eq("trim_start_match('ababf', 'ab')", "abf");
}

#[test]
fn trim_start() {
  assert_eval_eq("trim_start('  f  ')", "f  ");
}

#[test]
fn trim_end() {
  assert_eval_eq("trim_end('  f  ')", "  f");
}

#[test]
fn append() {
  assert_eval_eq("append('8', 'r s t')", "r8 s8 t8");
  assert_eval_eq("append('.c', 'main sar x11')", "main.c sar.c x11.c");
  assert_eval_eq("append('-', 'c v h y')", "c- v- h- y-");
  assert_eval_eq(
    "append('0000', '11 10 01 00')",
    "110000 100000 010000 000000",
  );
  assert_eval_eq(
    "append('tion', '
    Determina
    Acquisi
    Motiva
    Conjunc
    ')",
    "Determination Acquisition Motivation Conjunction",
  );
}

#[test]
fn prepend() {
  assert_eval_eq("prepend('8', 'r s t\n  \n  ')", "8r 8s 8t");
  assert_eval_eq(
    "prepend('src/', 'main sar x11')",
    "src/main src/sar src/x11",
  );
  assert_eval_eq("prepend('-', 'c\tv h\ny')", "-c -v -h -y");
  assert_eval_eq(
    "prepend('0000', '11 10 01 00')",
    "000011 000010 000001 000000",
  );
  assert_eval_eq(
    "prepend('April-', '
      1st,
        17th,
    20th,
    ')",
    "April-1st, April-17th, April-20th,",
  );
}

#[test]
fn join_unix() {
  if cfg!(windows) {
    return;
  }
  assert_eval_eq("join('a', 'b', 'c', 'd')", "a/b/c/d");
  assert_eval_eq("join('a', '/b', 'c', 'd')", "/b/c/d");
  assert_eval_eq("join('a', '/b', '/c', 'd')", "/c/d");
  assert_eval_eq("join('a', '/b', '/c', '/d')", "/d");
}

#[test]
fn join_windows() {
  if cfg!(not(windows)) {
    return;
  }
  assert_eval_eq("join('a', 'b', 'c', 'd')", "a\\b\\c\\d");
  assert_eval_eq("join('a', '\\b', 'c', 'd')", "\\b\\c\\d");
  assert_eval_eq("join('a', '\\b', '\\c', 'd')", "\\c\\d");
  assert_eval_eq("join('a', '\\b', '\\c', '\\d')", "\\d");
}

#[test]
fn join_argument_count_error() {
  Test::new()
    .justfile("x := join('a')")
    .args(["--evaluate"])
    .stderr(
      "
        error: function `join` called with 1 argument but takes 2 or more
         ——▶ justfile:1:6
          │
        1 │ x := join(\'a\')
          │      ^^^^
      ",
    )
    .failure();
}

#[test]
fn test_path_exists_filepath_exist() {
  Test::new()
    .tree(tree! {
      testfile: ""
    })
    .justfile("x := path_exists('testfile')")
    .args(["--evaluate", "x"])
    .stdout("true")
    .success();
}

#[test]
fn test_path_exists_filepath_doesnt_exist() {
  Test::new()
    .justfile("x := path_exists('testfile')")
    .args(["--evaluate", "x"])
    .stdout("false")
    .success();
}

#[test]
fn error_errors_with_message() {
  Test::new()
    .justfile("x := error ('Thing Not Supported')")
    .args(["--evaluate"])
    .stderr(
      "
        error: call to function `error` failed: Thing Not Supported
         ——▶ justfile:1:6
          │
        1 │ x := error ('Thing Not Supported')
          │      ^^^^^
      ",
    )
    .failure();
}

#[test]
fn test_absolute_path_resolves() {
  let test_object = Test::new()
    .justfile("path := absolute_path('./test_file')")
    .args(["--evaluate", "path"]);

  let mut tempdir = test_object.tempdir.path().to_owned();

  // Just retrieves the current directory via env::current_dir(), which
  // does the moral equivalent of canonicalize, which will remove symlinks.
  // So, we have to canonicalize here, so that we can match it.
  if cfg!(unix) {
    tempdir = tempdir.canonicalize().unwrap();
  }

  test_object
    .stdout(tempdir.join("test_file").to_str().unwrap().to_owned())
    .success();
}

#[test]
fn test_absolute_path_resolves_parent() {
  let test_object = Test::new()
    .justfile("path := absolute_path('../test_file')")
    .args(["--evaluate", "path"]);

  let mut tempdir = test_object.tempdir.path().to_owned();

  // Just retrieves the current directory via env::current_dir(), which
  // does the moral equivalent of canonicalize, which will remove symlinks.
  // So, we have to canonicalize here, so that we can match it.
  if cfg!(unix) {
    tempdir = tempdir.canonicalize().unwrap();
  }

  test_object
    .stdout(
      tempdir
        .parent()
        .unwrap()
        .join("test_file")
        .to_str()
        .unwrap()
        .to_owned(),
    )
    .success();
}

#[test]
fn path_exists_subdir() {
  Test::new()
    .tree(tree! {
      foo: "",
      bar: {
      }
    })
    .justfile("x := path_exists('foo')")
    .current_dir("bar")
    .args(["--evaluate", "x"])
    .stdout("true")
    .success();
}

#[test]
fn uuid() {
  Test::new()
    .justfile("x := uuid()")
    .args(["--evaluate", "x"])
    .stdout_regex("........-....-....-....-............")
    .success();
}

#[test]
fn choose() {
  Test::new()
    .justfile("x := choose('10', 'xXyYzZ')")
    .args(["--evaluate", "x"])
    .stdout_regex("^[X-Zx-z]{10}$")
    .success();
}

#[test]
fn choose_bad_alphabet_empty() {
  Test::new()
    .justfile("x := choose('10', '')")
    .args(["--evaluate"])
    .stderr(
      "
        error: call to function `choose` failed: empty alphabet
         ——▶ justfile:1:6
          │
        1 │ x := choose('10', '')
          │      ^^^^^^
      ",
    )
    .failure();
}

#[test]
fn choose_bad_alphabet_repeated() {
  Test::new()
    .justfile("x := choose('10', 'aa')")
    .args(["--evaluate"])
    .stderr(
      "
        error: call to function `choose` failed: alphabet contains repeated character `a`
         ——▶ justfile:1:6
          │
        1 │ x := choose('10', 'aa')
          │      ^^^^^^
      ",
    )
    .failure();
}

#[test]
fn choose_bad_length() {
  Test::new()
    .justfile("x := choose('foo', HEX)")
    .args(["--evaluate"])
    .stderr(
      "
        error: call to function `choose` failed: failed to parse `foo` as positive integer: invalid digit found in string
         ——▶ justfile:1:6
          │
        1 │ x := choose('foo', HEX)
          │      ^^^^^^
      ",
    )
    .failure();
}

#[test]
fn sha256() {
  Test::new()
    .justfile("x := sha256('5943ee37-0000-1000-8000-010203040506')")
    .args(["--evaluate", "x"])
    .stdout("2330d7f5eb94a820b54fed59a8eced236f80b633a504289c030b6a65aef58871")
    .success();
}

#[test]
fn sha256_file() {
  Test::new()
    .justfile("x := sha256_file('sub/shafile')")
    .tree(tree! {
      sub: {
        shafile: "just is great\n",
      }
    })
    .current_dir("sub")
    .args(["--evaluate", "x"])
    .stdout("177b3d79aaafb53a7a4d7aaba99a82f27c73370e8cb0295571aade1e4fea1cd2")
    .success();
}

#[test]
fn just_pid() {
  let Output { stdout, pid, .. } = Test::new()
    .args(["--evaluate", "x"])
    .justfile("x := just_pid()")
    .stdout_regex(r"\d+")
    .success();

  assert_eq!(stdout.parse::<u32>().unwrap(), pid);
}

#[test]
fn shell_no_argument() {
  Test::new()
    .justfile("var := shell()")
    .args(["--evaluate"])
    .stderr(
      "
        error: function `shell` called with 0 arguments but takes 1 or more
         ——▶ justfile:1:8
          │
        1 │ var := shell()
          │        ^^^^^
      ",
    )
    .failure();
}

#[test]
fn shell_minimal() {
  assert_eval_eq("shell('echo $1 $2', 'justice', 'legs')", "justice legs");
}

#[test]
fn shell_args() {
  assert_eval_eq("shell('echo $@', 'justice', 'legs')", "justice legs");
}

#[test]
fn shell_first_arg() {
  assert_eval_eq("shell('echo $0')", "echo $0");
}

#[test]
fn shell_error() {
  Test::new()
    .justfile("var := shell('exit 1')")
    .args(["--evaluate"])
    .stderr(
      "
        error: call to function `shell` failed: process exited with status code 1
         ——▶ justfile:1:8
          │
        1 │ var := shell('exit 1')
          │        ^^^^^
      ",
    )
    .failure();
}

#[test]
fn blake3() {
  Test::new()
    .justfile("x := blake3('5943ee37-0000-1000-8000-010203040506')")
    .args(["--evaluate", "x"])
    .stdout("026c9f740a793ff536ddf05f8915ea4179421f47f0fa9545476076e9ba8f3f2b")
    .success();
}

#[test]
fn blake3_file() {
  Test::new()
    .justfile("x := blake3_file('sub/blakefile')")
    .tree(tree! {
      sub: {
        blakefile: "just is great\n",
      }
    })
    .current_dir("sub")
    .args(["--evaluate", "x"])
    .stdout("8379241877190ca4b94076a8c8f89fe5747f95c62f3e4bf41f7408a0088ae16d")
    .success();
}

#[cfg(unix)]
#[test]
fn canonicalize() {
  Test::new()
    .args(["--evaluate", "x"])
    .justfile("x := canonicalize('foo')")
    .symlink("justfile", "foo")
    .stdout_regex(".*/justfile")
    .success();
}

#[test]
fn encode_uri_component() {
  Test::new()
    .justfile("x := encode_uri_component(\"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~ \\t\\r\\n🌐\")")
    .args(["--evaluate", "x"])
    .stdout("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!%22%23%24%25%26'()*%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~%20%09%0D%0A%F0%9F%8C%90")
    .success();
}

#[test]
fn source_file() {
  Test::new()
    .args(["--evaluate", "x"])
    .justfile("x := source_file()")
    .stdout_regex(r".*[/\\]justfile")
    .success();

  Test::new()
    .args(["--evaluate", "x"])
    .justfile(
      "
        import 'foo.just'
      ",
    )
    .write("foo.just", "x := source_file()")
    .stdout_regex(r".*[/\\]foo.just")
    .success();

  Test::new()
    .args(["foo", "bar"])
    .justfile(
      "
        mod foo
      ",
    )
    .write("foo.just", "x := source_file()\nbar:\n @echo '{{x}}'")
    .stdout_regex(r".*[/\\]foo.just\n")
    .success();
}

#[test]
fn source_directory() {
  Test::new()
    .args(["foo", "bar"])
    .justfile(
      "
        mod foo
      ",
    )
    .write(
      "foo/mod.just",
      "x := source_directory()\nbar:\n @echo '{{x}}'",
    )
    .stdout_regex(r".*[/\\]foo\n")
    .success();
}

#[test]
fn module_paths() {
  Test::new()
    .write(
      "foo/bar.just",
      "
imf := module_file()
imd := module_directory()

import-outer: import-inner

@import-inner pmf=module_file() pmd=module_directory():
  echo import
  echo '{{ imf }}'
  echo '{{ imd }}'
  echo '{{ pmf }}'
  echo '{{ pmd }}'
  echo '{{ module_file() }}'
  echo '{{ module_directory() }}'
      ",
    )
    .write(
      "baz/mod.just",
      "
import 'foo/bar.just'

mmf := module_file()
mmd := module_directory()

outer: inner

@inner pmf=module_file() pmd=module_directory():
  echo module
  echo '{{ mmf }}'
  echo '{{ mmd }}'
  echo '{{ pmf }}'
  echo '{{ pmd }}'
  echo '{{ module_file() }}'
  echo '{{ module_directory() }}'
      ",
    )
    .write(
      "baz/foo/bar.just",
      "
imf := module_file()
imd := module_directory()

import-outer: import-inner

@import-inner pmf=module_file() pmd=module_directory():
  echo import
  echo '{{ imf }}'
  echo '{{ imd }}'
  echo '{{ pmf }}'
  echo '{{ pmd }}'
  echo '{{ module_file() }}'
  echo '{{ module_directory() }}'
      ",
    )
    .justfile(
      "
        import 'foo/bar.just'
        mod baz

        rmf := module_file()
        rmd := module_directory()

        outer: inner

        @inner pmf=module_file() pmd=module_directory():
          echo root
          echo '{{ rmf }}'
          echo '{{ rmd }}'
          echo '{{ pmf }}'
          echo '{{ pmd }}'
          echo '{{ module_file() }}'
          echo '{{ module_directory() }}'
      ",
    )
    .args([
      "outer",
      "import-outer",
      "baz",
      "outer",
      "baz",
      "import-outer",
    ])
    .stdout_regex(
      r"root
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
import
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
module
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
import
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
",
    )
    .success();
}

#[test]
fn is_dependency() {
  let justfile = "
        alpha: beta
          @echo 'alpha {{is_dependency()}}'
        beta: && gamma
          @echo 'beta {{is_dependency()}}'
        gamma:
          @echo 'gamma {{is_dependency()}}'
      ";
  Test::new()
    .args(["alpha"])
    .justfile(justfile)
    .stdout("beta true\ngamma true\nalpha false\n")
    .success();

  Test::new()
    .args(["beta"])
    .justfile(justfile)
    .stdout("beta false\ngamma true\n")
    .success();
}

#[test]
fn unary_argument_count_mismatch_error_message() {
  Test::new()
    .justfile("x := datetime()")
    .args(["--evaluate"])
    .stderr(
      "
        error: function `datetime` called with 0 arguments but takes 1
         ——▶ justfile:1:6
          │
        1 │ x := datetime()
          │      ^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn dir_abbreviations_are_accepted() {
  Test::new()
    .justfile(
      "
        abbreviated := justfile_dir()
        unabbreviated := justfile_directory()

        @foo:
          # {{ assert(abbreviated == unabbreviated, 'fail') }}
      ",
    )
    .success();
}

#[test]
fn invocation_dir_native_abbreviation_is_accepted() {
  Test::new()
    .justfile(
      "
        abbreviated := invocation_directory_native()
        unabbreviated := invocation_dir_native()

        @foo:
          # {{ assert(abbreviated == unabbreviated, 'fail') }}
      ",
    )
    .success();
}

#[test]
fn absolute_path_argument_is_relative_to_submodule_working_directory() {
  Test::new()
    .justfile("mod foo")
    .write("foo/baz", "")
    .write(
      "foo/mod.just",
      r#"
bar:
  @echo "{{ absolute_path('baz') }}"
"#,
    )
    .stdout_regex(r".*[/\\]foo[/\\]baz\n")
    .args(["foo", "bar"])
    .success();
}

#[test]
fn blake3_file_argument_is_relative_to_submodule_working_directory() {
  Test::new()
    .justfile("mod foo")
    .write("foo/baz", "")
    .write(
      "foo/mod.just",
      "
bar:
  @echo {{ blake3_file('baz') }}
",
    )
    .stdout("af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262\n")
    .args(["foo", "bar"])
    .success();
}

#[test]
fn canonicalize_argument_is_relative_to_submodule_working_directory() {
  Test::new()
    .justfile("mod foo")
    .write("foo/baz", "")
    .write(
      "foo/mod.just",
      r#"
bar:
  @echo "{{ canonicalize('baz') }}"
"#,
    )
    .stdout_regex(r".*[/\\]foo[/\\]baz\n")
    .args(["foo", "bar"])
    .success();
}

#[test]
fn path_exists_argument_is_relative_to_submodule_working_directory() {
  Test::new()
    .justfile("mod foo")
    .write("foo/baz", "")
    .write(
      "foo/mod.just",
      "
bar:
  @echo {{ path_exists('baz') }}
",
    )
    .stdout_regex("true\n")
    .args(["foo", "bar"])
    .success();
}

#[test]
fn sha256_file_argument_is_relative_to_submodule_working_directory() {
  Test::new()
    .justfile("mod foo")
    .write("foo/baz", "")
    .write(
      "foo/mod.just",
      "
bar:
  @echo {{ sha256_file('baz') }}
",
    )
    .stdout_regex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n")
    .args(["foo", "bar"])
    .success();
}

#[test]
fn style_command_default() {
  Test::new()
    .justfile(
      "
        foo:
          @echo '{{ style('command') }}foo{{NORMAL}}'
      ",
    )
    .stdout("\x1b[1mfoo\x1b[0m\n")
    .success();
}

#[test]
fn style_command_non_default() {
  Test::new()
    .justfile(
      "
        foo:
          @echo '{{ style('command') }}foo{{NORMAL}}'
      ",
    )
    .args(["--command-color", "red"])
    .stdout("\x1b[1;31mfoo\x1b[0m\n")
    .success();
}

#[test]
fn style_error() {
  Test::new()
    .justfile(
      "
        foo:
          @echo '{{ style('error') }}foo{{NORMAL}}'
      ",
    )
    .stdout("\x1b[1;31mfoo\x1b[0m\n")
    .success();
}

#[test]
fn style_warning() {
  Test::new()
    .justfile(
      "
        foo:
          @echo '{{ style('warning') }}foo{{NORMAL}}'
      ",
    )
    .stdout("\x1b[1;33mfoo\x1b[0m\n")
    .success();
}

#[test]
fn style_unknown() {
  Test::new()
    .justfile(
      "
        foo:
          @echo '{{ style('hippo') }}foo{{NORMAL}}'
      ",
    )
    .stderr(
      "
        error: call to function `style` failed: unknown style: `hippo`
         ——▶ justfile:2:13
          │
        2 │   @echo '{{ style('hippo') }}foo{{NORMAL}}'
          │             ^^^^^
      ",
    )
    .failure();
}

#[test]
fn read() {
  Test::new()
    .justfile("foo := read('bar')")
    .write("bar", "baz")
    .args(["--evaluate", "foo"])
    .stdout("baz")
    .success();
}

#[test]
fn read_file_not_found() {
  Test::new()
    .justfile("foo := read('bar')")
    .args(["--evaluate", "foo"])
    .stderr_regex(r"error: call to function `read` failed: I/O error reading `bar`: .*")
    .failure();
}

#[test]
fn shell_with_powershell() {
  if !cfg!(windows) {
    return;
  }

  Test::new()
    .justfile(
      "
        set windows-shell := ['pwsh.exe', '-NoLogo', '-Command']

        foo := shell('Write-Output bar')

        default:
          @echo {{foo}}
      ",
    )
    .shell(false)
    .stdout("bar\r\n")
    .success();
}

#[test]
fn module_path() {
  Test::new()
    .justfile("foo := module_path()")
    .args(["--evaluate", "foo"])
    .stdout("")
    .success();
}

#[test]
fn module_path_in_submodule() {
  Test::new()
    .write("foo.just", "mod bar")
    .write("bar.just", "baz := module_path()")
    .justfile("mod foo")
    .args(["--evaluate", "foo::bar::baz"])
    .stdout("foo::bar")
    .success();
}
